关于异步任务,为什么要捕获OperationCanceledException需要Wait()?

时间:2013-04-25 18:32:50

标签: c# asynchronous .net-4.0 exception-handling task-parallel-library

我正在关注示例代码here以了解异步任务。我已经修改了代码来编写任务工作与主要工作的一些输出。输出将如下所示:

enter image description here

我注意到如果我删除Wait()调用,程序运行相同,除了我无法捕获任务被取消时抛出的异常。有人可以解释在需要Wait()的幕后发生的事情,以便击中catch块吗?

一个警告,Visual Studio调试器将错误地停在Console.WriteLine(" - task work");行,并显示消息“OperationCanceledException未被用户代码处理”。当发生这种情况时,只需单击继续或按F5查看程序的其余部分。有关详细信息,请参阅http://blogs.msdn.com/b/pfxteam/archive/2010/01/11/9946736.aspx

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
class Program
{
  static void Main()
  {
     var tokenSource = new CancellationTokenSource();
     var cancellationToken = tokenSource.Token;

    // Delegate representing work that the task will do.
     var workDelegate 
            = (Action)
              (
                () =>
                   {
                       while (true)
                       {
                          cancellationToken.ThrowIfCancellationRequested(); 
              // "If task has been cancelled, throw exception to return"

          // Simulate task work
                  Console.WriteLine(" - task work"); //Visual Studio  
           //erroneously stops on exception here. Just continue (F5). 
           //See http://blogs.msdn.com/b/pfxteam/archive/2010/01/11/9946736.aspx
                          Thread.Sleep(100);
                       }
                   }
              );


     try
     {
     // Start the task
         var task = Task.Factory.StartNew(workDelegate, cancellationToken);

      // Simulate main work
         for (var i = 0; i < 5; i++)
         {
             Console.WriteLine("main work");
             Thread.Sleep(200);
         }

       // Cancel the task
         tokenSource.Cancel();

       // Why is this Wait() necessary to catch the exception?
       // If I reomve it, the catch (below) is never hit, 
       //but the program runs as before.
          task.Wait();
     }
     catch (AggregateException e)
     {
         Console.WriteLine(e.Message);
         foreach (var innerException in e.InnerExceptions)
         Console.WriteLine(innerException.Message);
     }

     Console.WriteLine("Press any key to exit...");
     Console.ReadKey();
   }
}

3 个答案:

答案 0 :(得分:6)

ThrowIfCancellationRequested抛出异常时,它会从Task代理传播出来。此时,它被框架捕获并添加到Task的例外列表中。与此同时,Task已转换为Faulted州。

理想情况下,您希望观察所有Task例外情况。如果您使用Task作为基于任务的异步模式的一部分,那么在某些时候您应该await Task,它会传播Task上的第一个例外}。如果您使用Task作为任务并行库的一部分,那么在某些时候您应该调用WaitTask<T>.Result,它会传播Task上的所有例外情况,包裹在AggregateException

如果您没有观察到Task异常,那么当Task完成后,运行时将引发TaskScheduler.UnobservedTaskException,然后忽略该异常。 (这是.NET 4.5的行为; 4.5之前的行为会引发UnobservedTaskException,然后终止进程。)

在您的情况下,您不必等待任务完成,因此退出try / catch块。一段时间后,UnobservedTaskException被引发,然后忽略该异常。

答案 1 :(得分:1)

如果您不等待任务完成,您的程序将继续运行,因此您将通过catch-block。

另一方面,如果你await任务,那么异常仍将被捕获

答案 2 :(得分:1)

如果您不等待task完成,您的程序将在抛出异常之前退出(或至少离开该执行区域)。

如果主线程已经在

Console.ReadKey();

它不会“备份”到try/catch块。