为什么异步无效异常会使应用程序崩溃,但异步任务却被吞没 [英] Why does Exception from async void crash the app but from async Task is swallowed

查看:204
本文介绍了为什么异步无效异常会使应用程序崩溃,但异步任务却被吞没的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道异步任务的异常可以通过以下方式捕获:

I understand that an async Task's Exceptions can be caught by:

try { await task; }
catch { }

异步无效之所以不能,因为无法等待它。

while an async void's cannot because it cannot be awaited.

但是为什么不等待异步 Task 时(就像异步 void 之一)吞下了 Exception ,而 void 导致应用程序崩溃了?

But why is it that when the async Task is not awaited (just like the async void one) the Exception is swallowed, while the void's one crashes the application?

呼叫者 ex();

被叫

async void ex() { throw new Exception(); }
async Task ex() { throw new Exception(); }


推荐答案

TL; DR



这是因为不应使用 async void async void 仅用于使旧代码起作用(例如WindowsForms和WPF中的事件处理程序)。

TL;DR

This is because async void shouldn't be used! async void is only there to make legacy code work (e.g. event handlers in WindowsForms and WPF).

这是因为C#编译器如何为 async 方法生成代码。

This is because of how the C# compiler generates code for the async methods.

您应该知道在 async / await 后面有一个状态机( IAsyncStateMachine 实现)。

You should know that behind async/await there's a state machine (IAsyncStateMachine implementation) generated by the compiler.

当您声明 async 方法,将为其生成状态机 struct 。对于您的 ex()方法,此状态机代码如下所示:

When you declare an async method, a state machine struct will be generated for it. For your ex() method, this state machine code will look like:

void IAsyncStateMachine.MoveNext()
{
    try
    {
        throw new Exception();
    }
    catch (Exception exception)
    {
        this.state = -2;
        this.builder.SetException(exception);
    }
}

请注意, this.builder .SetException(exception); 语句。对于 Task -返回 async 方法,这将是 AsyncTaskMethodBuilder 对象。对于 void ex()方法,它将是 AsyncVoidMethodBuilder

Note that this.builder.SetException(exception); statement. For a Task-returning async method, this will be an AsyncTaskMethodBuilder object. For a void ex() method, it will be an AsyncVoidMethodBuilder.

ex()方法主体将由编译器替换为以下内容:

The ex() method body will be replaced by the compiler with something like this:

private static Task ex()
{
    ExAsyncStateMachine exasm;
    exasm.builder = AsyncTaskMethodBuilder.Create();
    exasm.state = -1;
    exasm.builder.Start<ExAsyncStateMachine>(ref exasm);
    return exasm.builder.Task;
}

(对于 async void ex(),将没有最后一个返回行)

(and for the async void ex(), there will be no last return line)

方法构建器的 Start< T> 方法将调用状态机的 MoveNext 方法。状态机的方法在其 catch 块中捕获异常。通常应在 Task 对象上观察到此异常- AsyncTaskMethodBuilder.SetException 方法将该异常对象存储在 Task 实例。当我们删除该 Task 实例(没有 await )时,我们根本看不到异常,但是该异常本身不再被抛出。

The method builder's Start<T> method will call the MoveNext method of the state machine. The state machine's method catches the exception in its catch block. This exception should normally be observed on the Task object - the AsyncTaskMethodBuilder.SetException method stores that exception object in the Task instance. When we drop that Task instance (no await), we don't see the exception at all, but the exception itself isn't thrown anymore.

async void ex()的状态机中,有一个 AsyncVoidMethodBuilder 。它的 SetException 方法看起来与众不同:由于没有 Task 存储异常的位置,因此必须抛出该异常。但是,它发生的方式有所不同,不仅仅是普通的投掷

In the state machine for async void ex(), there's an AsyncVoidMethodBuilder instead. Its SetException method looks different: since there's no Task where to store the exception, it has to be thrown. It happens in a different way, however, not just a normal throw:

AsyncMethodBuilderCore.ThrowAsync(exception, synchronizationContext);

AsyncMethodBuilderCore.ThrowAsync 内的逻辑决定:


  • 是否存在 SynchronizationContext (例如,我们在UI线程上) WPF应用程序),该异常将在该上下文中发布

  • 否则,该异常将在 ThreadPool 线程。

  • If there's a SynchronizationContext (e.g. we're on a UI thread of a WPF app), the exception will be posted on that context.
  • Otherwise, the exception will be queued on a ThreadPool thread.

在两种情况下,都不会捕获异常可能在 ex()调用周围设置的try-catch 块(除非您具有特殊的 SynchronizationContext 可以执行此操作,例如,请参阅Stephen Cleary的 AsyncContext )。

In both cases, the exception won't be caught by a try-catch block that might be set up around the ex() call (unless you have a special SynchronizationContext that can do this, see e.g. Stephen Cleary's AsyncContext).

原因很简单:当我们发布进行动作或入队,然后我们只需从 ex()甲基od,然后离开 try-catch 块。然后,执行发布/排队操作(在相同或不同线程上)。

The reason is simple: when we post a throw action or enqueue it, we then simply return from the ex() method and thus leave the try-catch block. Then, the posted/enqueued action is executed (either on the same or on a different thread).

这篇关于为什么异步无效异常会使应用程序崩溃,但异步任务却被吞没的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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