如何等待取消的任务完成?

时间:2012-08-17 00:10:18

标签: .net c#-4.0 parallel-processing

在使用.NET 4.0进行并行编程时,我显然不知道自己在做什么。我有一个简单的Windows应用程序启动任务做一些盲目的工作(输出数字1-1000)。我在中途进行了大量的停顿,以模拟一个长时间运行的过程。当这个长时间暂停发生时,如果我点击Stop按钮,它的事件处理程序会调用CancellationTokenSource的Cancel方法。我不希望在Stop按钮的事件处理程序中进行任何进一步处理(在这种情况下,输出消息),直到取消的任务完成其当前迭​​代。我该怎么做呢?我尝试在Stop按钮的事件处理程序中使用Task.WaitAll等,但这只会抛出一个未处理的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();
  }

对此的任何帮助将不胜感激。提前谢谢。

2 个答案:

答案 0 :(得分:10)

您通常只使用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()或尝试获取任务Result后立即被抛出1}}(如果它是Task<T>)。


纯粹是为了您的信息 - 这是一个地方,特别是考虑到您在这里所做的事情,C#5简化了事情。使用新的异步支持,您可以将其写为:

// 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();
}

答案 1 :(得分:4)

抱歉,没有足够的声誉只是添加一个问题作为对该答案的评论。我想问一下如何将解决方案作为答案。

答案中的代码是否依赖于某种用户界面调整来阻止用户一次启动多个后台进程?当然,这总是一个问题。

在这里,我提出了一个回答所述问题的替代方案。它显示了如何等待取消请求完成。它以一种方式执行此操作,如果管理不善,仍会让UI搞砸了,但是如果真正需要的话,代码实际上会在取消后等待。这是一个较大的C#类的摘录:

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()函数。但是接受的答案更多的是解决方法,没有冒犯(也许我错了?)。