在方法签名中使用异步时任务取消异常不会冒泡

Posted

技术标签:

【中文标题】在方法签名中使用异步时任务取消异常不会冒泡【英文标题】:Task cancellation exception not bubbling up when using async in method signature 【发布时间】:2012-12-04 05:40:00 【问题描述】:

我真的不明白这一点。在我下面的代码中,按原样运行,token.ThrowIfCancellationRequested() 抛出的异常不会被发送回 Main 使用 .Wait() 的位置,而是当场抛出,堆栈跟踪并不知道它发生在哪里除了“任务已取消”。但是,如果我删除 async 关键字并使用 await Task.Delay() 删除 try catch 块,它确实会被发送回 main 中的 .Wait() 并被捕获。

我做错了什么,或者我究竟如何让 await Task.Delay() 和 token.ThrowIfCancellationRequested() 引发的异常都冒泡到 .Wait()?

static void Main(string[] args)

    var t = new CancellationTokenSource();
    var task = Task.Factory.StartNew((x) => Monitor(t.Token), t.Token, TaskCreationOptions.LongRunning);

    while (true)
    
        if (Console.ReadLine() == "cancel")
        
            t.Cancel();
            try 
            
                task.Wait();
            
            catch(AggregateException)
            
                Console.WriteLine("Exception via task.Wait()");

            
            Console.WriteLine("Cancelled");
        
    


static async void Monitor(CancellationToken token)

    while(true)
    
        for(int i = 0; i < 5; i++)
        
            // If the async in the method signature, this does not send the exception to .Wait();
            token.ThrowIfCancellationRequested();

            // Do Work
            Thread.Sleep(2000);
        

        // Wait 10 seconds before doing work again.

        // When this try block is removed, and the async is taken out of the method signature,
        // token.ThrowIfCancellationRequested() properly sends the exception to .Wait()
        try
        
            await Task.Delay(10000, token);
         
        catch(TaskCanceledException) 
        
            Console.WriteLine("Exception from Delay()");
            return;
        
    

【问题讨论】:

你不应该在任务中使用异步,任务本身将异步运行。 【参考方案1】:

你应该避免async void。它的异常处理语义比较复杂,无法组合成其他的async 方法。

async void 方法在概念上是事件处理程序,因此如果它引发异常,它将直接在其 SynchronizationContext 上引发 - 在这种情况下,在线程池线程上引发。

void-returning 方法的异步等价物不是async void-returning 方法;这是一个asyncTask-returning 方法。所以你的Monitor 方法应该返回Task

static void Main(string[] args)

  var t = new CancellationTokenSource();
  var task = Monitor(t.Token);

  while (true)
  
    if (Console.ReadLine() == "cancel")
    
      t.Cancel();
      try 
      
        task.Wait();
      
      catch(AggregateException)
      
        Console.WriteLine("Exception via task.Wait()");
      
      Console.WriteLine("Cancelled");
    
  


static async Task Monitor(CancellationToken token)

不用担心错过LongRunning 标志;这只是一种优化,没有它,线程池也能正常工作。

您可能会发现我的async/await intro 或official MSDN documentation 很有帮助。

【讨论】:

有趣。谢谢一堆。 :-)

以上是关于在方法签名中使用异步时任务取消异常不会冒泡的主要内容,如果未能解决你的问题,请参考以下文章

Task异步编程

当您的方法签名不允许抛出异常时如何抛出异常?

如何在静态方法中取消分配使用std :: memory_resource分配的内存而不更改下面的函数签名

使用异步方法等待 Task.Run 不会在正确的线程上引发异常

支付宝接口的数字签名

如何对带有异步的流进行单元测试