Try / Catch Block中未完成任务会发生什么

时间:2017-11-15 19:54:02

标签: c# async-await

当方法返回Task块中包含的try/catch时会发生什么?如果那个未等待的Task抛出异常,是否会捕获/处理异常?

例如,如果DoSomethingAsync()引发异常,我可以在try/catch的{​​{1}}块中处理吗?

TryCatchMethod()

4 个答案:

答案 0 :(得分:6)

如果DoSomethingAsync抛出异常,则会捕获异常。如果它返回一个错误的任务,而不是抛出,那么在你试图获得任务的结果(通过等待它)之前,没有异常可以捕获。由于您未在try块中执行此操作,因此不会运行catch块。

请注意,如果方法标记为async,则会捕获该方法正文中抛出的任何异常,并导致该方法返回错误的任务,而不是抛出异常的方法。对于抛出异常的方法,该方法需要返回Task而不被标记为async

答案 1 :(得分:5)

答案

不,try/catch中的TryCatchMethod()阻止因{2}原因DoSomethingAsync()中出现异常而无法捕获异常:

  1. 该异常将在MoveNext()
  2. 中捕获
  3. DoSomethingAsync()不是await d,因此永远不会重新抛出异常
  4. 解决方案

    1. 等待DoSomethingAsync() (首选)
    2. async Task TryCatchMethod()
      {
          try
          {
              return await DoSomethingAsync();
          }
          catch(Exception e)
          {
              //Handle Exception
          }
      }
      1. 使用.GetAwaiter().GetResult()
      2. void TryCatchMethod()
        {
            try
            {
               DoSomethingAsync().GetAwaiter().GetResult();
            }
            catch(Exception e)
            {
                //Handle Exception
            }
        }

        注意:建议不要使用.GetAwaiter().GetResult(),因为它会锁定当前线程。

        解释

        要理解try/catch块没有捕获异常的原因,首先要了解编译器如何为async方法生成IL代码非常重要。

        编译器生成的异步方法代码

        enter image description here

        (资料来源:Xamarin University: Using Async and Await

        编译器将async方法转换为IAsyncStateMachine类,允许.NET运行时“记住”方法已完成的内容。

        enter image description here

        (资料来源:Xamarin University: Using Async and Await

        IAsyncStateMachine接口实现MoveNext(),这是一种在await方法中每次使用async运算符时执行的方法。

        MoveNext()基本上运行代码,直到它达到await语句,然后return执行await方法时MoveNext()。这是允许当前方法“暂停”的机制,使其线程执行产生于另一个线程/任务。

        尝试/抓住MoveNext()

        密切关注try/catch;注意它被包裹在IAsyncStateMachine块中。

        因为编译器为每个async方法创建MoveNext()并且try/catch 总是包裹在async中,所以每个异常都会抛出抓住了MoveNext方法!

        如何重新抛出由async

        捕获的异常

        现在的问题是,如果每个async方法捕获到抛出的每个异常,我该如何重新抛出异常?

        有几种方法可以重新抛出await方法中抛出的异常:

        1. 使用await DoSomethingAsync()关键字(首选)
          • e.g。 .GetAwaiter().GetResult()
        2. 使用DoSomethingAsync().GetAwaiter().GetResult()
          • e.g。 await
        3. 首选await关键字是因为Task允许.Result在不同的线程上异步运行,并且它不会锁定当前线程。

          .Wait().Result怎么办?

          从不,永远,从不,永远,永远不要使用.Wait().Result

          1. .Wait()Task都将锁定当前线程。如果当前线程是主线程(也称为UI线程),则UI将冻结,直到.Result完成。 2. .Wait()System.AggregateException将您的例外重新抛出为var longRunningTask1 = LongRunningTaskAsync(); var longRunningTask2 = LongRunningTaskAsync(); await Task.WhenAll(longRunningTask1, longRunningTask2); var result1 = longRunningTask1.Result; var result2 = longRunningTask2.Result; ,这使得很难找到实际的例外。
          2. 实施例

            不要这样做

            var longRunningTask1 = LongRunningTaskAsync();
            var longRunningTask2 = LongRunningTaskAsync();
            
            await Task.WhenAll(longRunningTask1, longRunningTask2);
            
            var result1 = await longRunningTask1;
            var result2 = await longRunningTask2;
            

            相反,请执行此操作

            --jars

答案 2 :(得分:1)

等待任务时抛出异常。当等待关键字等待任务时,编译器生成代码巧妙地解开异常并抛出它们对合理合理的堆栈跟踪的期望。如果您访问任务的结果或调用等待,但它被抛出作为聚合异常,这也是抛出,这不是最好的做法。如果您从未等待任务,则异常最终会成为未经执行的任务异常,并且对您的程序可能是致命的,并且还有垃圾堆栈跟踪

答案 3 :(得分:0)

使用await tasktask.Resulttask Wait()访问任务执行结果时,将抛出异常。

在您的情况下,如果在DoSomethingAsync()内的任务内引发异常,它将不会被TryCatchMethod()捕获,但会被等待{{1返回的任务的外部方法捕获}}

查看更多详情:Exception Handling (Task Parallel Library)