为什么异步无效异常会使应用程序崩溃,但异步任务却被吞没 [英] Why does Exception from async void crash the app but from async Task is swallowed
问题描述
我知道异步任务
的异常可以通过以下方式捕获:
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()$ c $返回c>甲基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屋!