为什么在构造函数中的异步操作中会吞下异常?

Pro*_*ofK 0 c# constructor asynchronous anonymous-function async-await

我以为我很聪明地找到了一种在ctor中调用aync方法的方法:

public AppStateModel(IBranchClient branchClient)
{
    _branchClient = branchClient;
    var loadBranch = new Action(async () =>
    {
        DataProviderReadResult<BranchDetailViewModel> result = await _branchClient.ReadOneItemAsync(AppSettings.BranchId, _initCts.Token);
    });
    loadBranch();
}
Run Code Online (Sandbox Code Playgroud)

但是动作的主体抛出一个异常,我用普通的方式记录并重新抛出throw;,但是这个ctor执行正常,我的其余代码继续运行,好像什么也没发生.为什么是这样?

Lua*_*aan 7

因为动作是异步的.loadBranch()一旦达到第一个就返回await,并且不可能抛出你期望的异常 - 这Task是你忽略的信息的一部分(通过使用Action而不是Func<Task>).

总而言之,您刚刚编写了一个更加混淆的版本:

_branchClient.ReadOneItemAsync(AppSettings.BranchId, _initCts.Token);
Run Code Online (Sandbox Code Playgroud)

.NET构造函数本质上是同步的.它们不应该在您使用异步代码的情况下做任何事情 - 并且在构造函数(或构造函数调用的方法)中尽可能少地做它是个好主意.如果您需要复杂的操作,异步代码,I/O,大量的CPU工作...使用静态方法.因为你正在运行异步代码,所以让它返回a Task<AppStateModel>,在整个异步流程中正确拟合.

另请注意,旧的.NET运行时不会吞下异常.假设没有同步上下文,异常仍然在后台线程上抛出(其中发布了异步操作的延续) - 并且线程池线程上未处理的异常的默认值曾经是"降低整个应用程序".当Task对象被最终确定时会发生这种情况,因此与所有程序逻辑分离,就你所知,它几乎是随机的.毕竟,你还能做些什么 - 没有可以观察到异常的好地方,那里唯一的根本就是"我不关心这项任务会发生什么".但考虑到确保正确观察和处理每个异常的复杂程度,默认值已更改为"忽略未观察到的异常".