Подтвердить что ты не робот

Как подождать завершения отложенной задачи?

Я, очевидно, не знаю, что я делаю, когда речь идет о параллельном программировании с .NET 4.0. У меня есть простое приложение для Windows, которое запускает задачу для выполнения бессмысленной работы (вывод чисел 1-1000). Я поставил существенную паузу на полпути, чтобы имитировать длительный процесс. Хотя эта длительная пауза происходит, если я нажму кнопку Stop, ее обработчик событий вызывает метод отмены CancellationTokenSource. Я не хочу делать дальнейшую обработку (в этом случае вывод сообщения) в обработчике событий кнопки Stop, пока отмененная задача не будет выполнена с ее текущей итерацией. Как мне это сделать? Я попытался использовать Task.WaitAll и т.д. В обработчике событий кнопки Stop, но это просто выбрасывает необработанное исключение AggregateException. Вот код, который поможет объяснить мою проблему, если вы запустите, как описано выше:

  private Task t;
  private CancellationTokenSource cts;

  public Form1()
  {
     InitializeComponent();
  }

  private void startButton_Click(object sender, EventArgs e)
  {
     statusTextBox.Text = "Output started.";

     // Create the cancellation token source.
     cts = new CancellationTokenSource();

     // Create the cancellation token.
     CancellationToken ct = cts.Token;

     // Create & start worker task.
     t = Task.Factory.StartNew(() => DoWork(ct), ct);
  }

  private void DoWork(CancellationToken ct)
  {
     for (int i = 1; i <= 1000; i++)
     {
        ct.ThrowIfCancellationRequested();

        Thread.Sleep(10);  // Slow down for text box outout.
        outputTextBox.Invoke((Action)(() => outputTextBox.Text = i + Environment.NewLine));

        if (i == 500)
        {
           Thread.Sleep(5000);
        }
     }
  }

  private void stopButton_Click(object sender, EventArgs e)
  {
     cts.Cancel();

     Task.WaitAll(t);  // this doesn't work :-(

     statusTextBox.Text = "Output ended.";
  }

  private void exitButton_Click(object sender, EventArgs e)
  {
     this.Close();
  }

Любая помощь с этим будет очень признательна. Спасибо заранее.

4b9b3361

Ответ 1

Обычно вы просто используете Task.Wait (вместо WaitAll), поскольку это единственная задача. а затем обработал исключение соответствующим образом:

private void stopButton_Click(object sender, EventArgs e)
{
    cts.Cancel();
    try
    {
        t.Wait();  // This will throw
    }
    catch (AggregateException ae)
    {
       ae.Handle<OperationCanceledException>(ce => true);
    }

    statusTextBox.Text = "Output ended.";
}

Когда вы отмените Task, OperationCanceledException будет завернуто в AggregateException и будет выброшено, как только вы вызовете Wait() или попытаетесь получить Task Result (если это a Task<T>).


Чисто для вашей информации. Это одно место, особенно учитывая то, что вы здесь делаете, где С# 5 упрощает работу. Используя новую поддержку async, вы можете написать это как:

// No need for "t" variable anymore 
// private Task t;


private async void startButton_Click(object sender, EventArgs e)
{
   statusTextBox.Text = "Output started.";

   // Create the cancellation token source.
   cts = new CancellationTokenSource();

   try
   {
      // Create & start worker task.
      await Task.Run(() => DoWork(cts.Token));
      statusTextBox.Text = "Output ended.";
   }
   catch(OperationCanceledException ce) 
   {
      // Note that we get "normal" exception handling
      statusTextBox.Text = "Operation canceled.";
   }
}

private void stopButton_Click(object sender, EventArgs e)
{
   // Just cancel the source - nothing else required here
   cts.Cancel();
}

Ответ 2

Извините, недостаточно репутации, чтобы просто добавить вопрос в качестве комментария к этому ответу. Я хотел спросить, как обход считается ответом.

Не отвечает ли код в ответе на какой-то пользовательский интерфейс, чтобы удержать пользователя от запуска более одного фонового процесса за раз? Это всегда проблема, конечно.

Здесь я представляю альтернативу, которая отвечает на поставленный вопрос. Он показывает, как ждать завершения аннулирования. Он делает это таким образом, который все еще позволяет UI завинчивать вещи, если он плохо управляется, но имеет код, который на самом деле ждет после отмены, если это действительно необходимо. Это выдержка из более крупного класса С#:

AutoResetEvent m_TaskFinishedEvent = new AutoResetEvent( false );
private IAsyncAction m_Operation = null;

private Task WaitOnEventAsync( EventWaitHandle WaitForEvent )
{
    return Task.Run( () => { WaitForEvent.WaitOne(); } );
}

public async void Start()
{
if( m_Operation != null )
    {
        // Cancel existing task
        m_Operation.Cancel();
        // Wait for it to finish. This returns control to the UI.
        await WaitOnEventAsync( m_TaskFinishedEvent );
    }
    // Start the background task.
    m_Operation = ThreadPool.RunAsync( WorkerMethod );
}

private void WorkerMethod( IAsyncAction operation )
{
    while( m_Operation.Status != AsyncStatus.Canceled )
        ; // Add real work here.

    m_TaskFinishedEvent.Set();
}

Этот код полагается на объект события, чтобы сигнализировать, что задача в основном завершена. Код WorkerMethod() еще не возвращен, но вся полезная работа выполняется, когда событие сигнализируется.

Я не предоставлял функцию Stop() из-за того, как я использую этот код. Код для ожидания будет просто включен в эту функцию Stop(), если именно так должен работать код.

Да, вы не можете использовать регулярную функцию Wait() из-за исключения отмены. Но принятый ответ больше связан с работой, без обид (и, может быть, я ошибаюсь?).