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

时间:2022-09-02 02:20:19

I obviously don't know what I'm doing when it comes to parallel programming with .NET 4.0. I have a simple Windows app that starts a task to do some mindless work (outputting numbers 1-1000). I put a substantial pause in half way through to simulate a long running process. While this long pause is taking place, if I hit the Stop button, its event handler calls the Cancel method of CancellationTokenSource. I don't want to do any further processing (in this case, outputting a message) in the Stop button's event handler until the canceled task is done with its current iteration. How do I do this? I tried using Task.WaitAll, etc in the Stop button's event handler, but that just throws an unhandled AggregateException. Here's the code which will help explain my problem if run as described above:

在使用.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();
  }

Any help with this would be greatly appreciated. Thanks in advance.

任何有关这方面的帮助将不胜感激。提前致谢。

2 个解决方案

#1


9  

You would normally just use Task.Wait (instead of WaitAll), as it's a single task. and then handled the exception appropriately:

你通常只使用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.";
}

When you cancel a Task, the OperationCanceledException will get wrapped into an AggregateException and be thrown as soon as you call Wait() or try to get the Task's Result (if it's a Task<T>).

当您取消任务时,OperationCanceledException将被包装到AggregateException中,并在您调用Wait()或尝试获取Task的结果(如果它是Task )时抛出。


Purely for your information - This is one place, especially given what you're doing here, where C# 5 simplifies things. Using the new async support, you can write this as:

纯粹是为了您的信息 - 这是一个地方,特别是考虑到您在这里所做的事情,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();
}

#2


4  

Sorry, not enough reputation to just add a question as a comment to that answer. I wanted to ask how a work-around is considered an answer.

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

Doesn't the code in the answer rely on some sort of user interface tweak to keep the user from starting more than one background process at a time? That's always a problem, of course.

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

Here, I present an alternative that answer the question that is stated. It shows how to wait for the cancellation request to finish. It does this in a way that still lets the UI screw things up if it is not managed well, but has code that actually waits after the cancellation, if that is what is really needed. This is an excerpt from a larger C# class:

在这里,我提出了一个回答所述问题的替代方案。它显示了如何等待取消请求完成。它以一种方式执行此操作,如果管理不善,仍会让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();
}

This code relies on an event object to signal that the task is mostly finished. The WorkerMethod() code has not returned yet, but all useful work is done when the event is signaled.

此代码依赖于一个事件对象来表示任务大部分已完成。 WorkerMethod()代码尚未返回,但所有有用的工作都是在发出事件信号时完成的。

I did not provide a Stop() function because of how I use this code. The code to do the wait would just go in that Stop() function if that was how the code needs to work.

我没有提供Stop()函数,因为我使用这段代码。如果这是代码需要工作的方式,那么执行等待的代码就会进入Stop()函数。

Yes, you cannot use a regular Wait() function because of the cancellation exception. But the accepted answer is more of a work-around, no offense (and maybe I'm wrong?).

是的,由于取消异常,您无法使用常规的Wait()函数。但是接受的答案更多的是解决方法,没有冒犯(也许我错了?)。

#1


9  

You would normally just use Task.Wait (instead of WaitAll), as it's a single task. and then handled the exception appropriately:

你通常只使用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.";
}

When you cancel a Task, the OperationCanceledException will get wrapped into an AggregateException and be thrown as soon as you call Wait() or try to get the Task's Result (if it's a Task<T>).

当您取消任务时,OperationCanceledException将被包装到AggregateException中,并在您调用Wait()或尝试获取Task的结果(如果它是Task )时抛出。


Purely for your information - This is one place, especially given what you're doing here, where C# 5 simplifies things. Using the new async support, you can write this as:

纯粹是为了您的信息 - 这是一个地方,特别是考虑到您在这里所做的事情,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();
}

#2


4  

Sorry, not enough reputation to just add a question as a comment to that answer. I wanted to ask how a work-around is considered an answer.

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

Doesn't the code in the answer rely on some sort of user interface tweak to keep the user from starting more than one background process at a time? That's always a problem, of course.

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

Here, I present an alternative that answer the question that is stated. It shows how to wait for the cancellation request to finish. It does this in a way that still lets the UI screw things up if it is not managed well, but has code that actually waits after the cancellation, if that is what is really needed. This is an excerpt from a larger C# class:

在这里,我提出了一个回答所述问题的替代方案。它显示了如何等待取消请求完成。它以一种方式执行此操作,如果管理不善,仍会让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();
}

This code relies on an event object to signal that the task is mostly finished. The WorkerMethod() code has not returned yet, but all useful work is done when the event is signaled.

此代码依赖于一个事件对象来表示任务大部分已完成。 WorkerMethod()代码尚未返回,但所有有用的工作都是在发出事件信号时完成的。

I did not provide a Stop() function because of how I use this code. The code to do the wait would just go in that Stop() function if that was how the code needs to work.

我没有提供Stop()函数,因为我使用这段代码。如果这是代码需要工作的方式,那么执行等待的代码就会进入Stop()函数。

Yes, you cannot use a regular Wait() function because of the cancellation exception. But the accepted answer is more of a work-around, no offense (and maybe I'm wrong?).

是的,由于取消异常,您无法使用常规的Wait()函数。但是接受的答案更多的是解决方法,没有冒犯(也许我错了?)。