未通过await调用时在异步方法中捕获异常 [英] Catching Exceptions in async methods when not called with await

查看:56
本文介绍了未通过await调用时在异步方法中捕获异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

.Net Core库中的异常让我感到困惑.这个问题的目的是了解为什么正在做我所看到的事情.

I am confused by the behavior I am seeing with exceptions in my .Net Core library. The goal of this question is to understand why it is doing what I am seeing.

我认为,当调用 async 方法时,其中的代码将同步执行,直到遇到第一次等待.如果是这种情况,那么如果在该同步代码"期间引发了异常,为什么它不传播到调用方法呢?(就像普通的同步方法一样.)

I thought that when an async method is called, the code in it is executed synchronously until it hits the first await. If that is the case, then, if an exception is thrown during that "synchronous code", why is it not propagated up to the calling method? (As a normal synchronous method would do.)

在.Net Core控制台应用程序中给出以下代码:

Given the following code in a .Net Core Console Application:

static void Main(string[] args)
{
    Console.WriteLine("Hello World!");

    try
    {
        NonAwaitedMethod();
    }
    catch (Exception e)
    {
        Console.WriteLine("Exception Caught");
    }

    Console.ReadKey();
}

public static async Task NonAwaitedMethod()
{
    Task startupDone = new Task(() => { });
    var runTask = DoStuff(() =>
    {
        startupDone.Start();
    });
    var didStartup = startupDone.Wait(1000);
    if (!didStartup)
    {
        throw new ApplicationException("Fail One");
    }

    await runTask;
}

public static async Task DoStuff(Action action)
{
    // Simulate starting up blocking
    var blocking = 100000;
    await Task.Delay(500 + blocking);
    action();
    // Do the rest of the stuff...
    await Task.Delay(3000);
}

}

  1. 按原样运行时,此代码将引发异常,但是,除非您有断点,否则您将不会知道它.Visual Studio Debugger或控制台将给出任何问题的指示(除了输出"屏幕中的一行注释).

  1. When run as is, this code will throw an exception, but, unless you have a break point on it, you will not know it. The Visual Studio Debugger nor the Console will give any indication that there was an issue (aside from a one line note in the Output screen).

NonAwaitedMethod 的返回类型从 Task 交换为 void .这将导致Visual Studio调试器现在在异常时中断.它还将在控制台中打印出来.但值得注意的是,被捕获在 Main catch 语句中.

Swap the return type of NonAwaitedMethod from Task to void. This will cause the Visual Studio Debugger to now break on the exception. It will also be printed out in the console. But notably, the exception is NOT caught in the catch statement found in Main.

NonAwaitedMethod 的返回类型保留为 void ,但取消 async .还要将最后一行从 await runTask; 更改为 runTask.Wait(); (这实际上删除了所有异步内容.)运行时,异常会捕获在 Main 方法中> catch 语句.

Leave the return type of NonAwaitedMethod as void, but take off the async. Also change the last line from await runTask; to runTask.Wait(); (This essentially removes any async stuff.) When run, the exception is caught in the catch statement in the Main method.

所以,总结一下:

| Scenario   | Caught By Debugger | Caught by Catch |  
|------------|--------------------|-----------------|  
| async Task | No                 | No              |  
| async void | Yes                | No              |  
| void       | N/A                | Yes             |  

问题:

我认为因为异常是在 await 完成之前抛出的,例外.

Questions:

I thought that because the exception was thrown before an await was done, that it would execute synchronously up to, and through the throwing of the exception.

我的问题是:为什么场景1或2都不会被 catch 语句捕获?

Hence my question of: Why does neither scenario 1 or 2 get caught by the catch statement?

此外,为什么从 Task 转换为 void 返回类型会导致调试器捕获异常?(即使我没有使用该返回类型.)

Also, why does swapping from Task to void return type cause the exception to get caught by the Debugger? (Even though I am not using that return type.)

推荐答案

在等待完成之前抛出了异常,它会同步执行

exception was thrown before an await was done, that it would execute synchronously

这种说法是正确的,但这并不意味着您可以捕获该异常.

Thought this is fairly true, but it doesn't mean you could catch the exception.

因为您的代码具有 async 关键字,该关键字将方法转换为异步状态机,即由特殊类型封装/包装.当任务被 await 执行时(除了那些 async void 任务除外),或者被观察不到的情况下,从异步状态机抛出的任何异常都将被捕获并重新抛出.在 TaskScheduler.UnobservedTaskException 事件中捕获.

Because your code has async keyword, which turns the method into an async state machine i.e. encapsulated / wrapped by a special type. Any exception thrown from async state machine will get caught and re-thrown when the task is awaited (except for those async void ones) or they go unobserved, which can be caught in TaskScheduler.UnobservedTaskException event.

如果从 NonAwaitedMethod 方法中删除 async 关键字,则可以捕获该异常.

If you remove async keyword from the NonAwaitedMethod method, you can catch the exception.

观察此行为的一种好方法是使用此方法:

A good way to observe this behavior is using this:

try
{
    NonAwaitedMethod();

    // You will still see this message in your console despite exception
    // being thrown from the above method synchronously, because the method
    // has been encapsulated into an async state machine by compiler.
    Console.WriteLine("Method Called");
}
catch (Exception e)
{
    Console.WriteLine("Exception Caught");
}

因此,您的代码的编译与此类似:

So your code is compiled similarly to this:

try
{
    var stateMachine = new AsyncStateMachine(() =>
    {
        try
        {
            NonAwaitedMethod();
        }
        catch (Exception ex)
        {
            stateMachine.Exception = ex;
        }
    });

    // This does not throw exception
    stateMachine.Run();
}
catch (Exception e)
{
    Console.WriteLine("Exception Caught");
}

为什么从Task转换为void返回类型会导致捕获异常

why does swapping from Task to void return type cause the exception to get caught

如果该方法返回 Task ,则任务捕获异常.

If the method returns a Task, the exception is caught by the task.

如果方法是 void ,则从任意线程池线程重新抛出该异常.从线程池线程中抛出的任何未处理的异常都将导致应用程序崩溃,因此调试器(或JIT调试器)很有可能正在监视此类异常.

If the method is void, then the exception gets re-thrown from an arbitrary thread pool thread. Any unhandled exception thrown from thread pool thread will cause the app to crash, so chances are the debugger (or maybe the JIT debugger) is watching this sort of exceptions.

如果您希望解雇并忘记却正确处理了该异常,则可以使用

If you want to fire and forget but properly handle the exception, you could use ContinueWith to create a continuation for the task:

NonAwaitedMethod()
    .ContinueWith(task => task.Exception, TaskContinuationOptions.OnlyOnFaulted);

请注意,您必须访问 task.Exception 属性以观察到异常,否则,任务计划程序仍将接收 UnobservedTaskException 事件.

Note you have to visit task.Exception property to make the exception observed, otherwise, task scheduler still will receive UnobservedTaskException event.

或者如果需要在 Main 中捕获并处理异常,则正确的方法是使用

Or if the exception needs to be caught and processed in Main, the correct way to do that is using async Main methods.

这篇关于未通过await调用时在异步方法中捕获异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆