24

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

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

4

2 回答 2

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.";
}

当您取消 aTask时,OperationCanceledException将被包装成 anAggregateException并在您调用Wait()或尝试获取 Task 时立即被抛出Result(如果它是 a 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();
}
于 2012-08-17T00:16:43.337 回答
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() 函数。但接受的答案更多的是一种解决方法,没有冒犯(也许我错了?)。

于 2013-10-03T22:57:20.927 回答