我以为我很聪明地找到了一种在ctor中调用aync方法的方法:
public AppStateModel(IBranchClient branchClient)
{
_branchClient = branchClient;
var loadBranch = new Action(async () =>
{
DataProviderReadResult<BranchDetailViewModel> result = await _branchClient.ReadOneItemAsync(AppSettings.BranchId, _initCts.Token);
});
loadBranch();
}
但是动作的主体抛出一个异常,我用普通throw;
记录并重新抛出,但是这个ctor执行正常,我的其余代码继续运行,好像没有任何反应。这是为什么?
答案 0 :(得分:7)
因为动作是异步的。 loadBranch()
会在到达第一个await
后立即返回,并且无法抛出您期望的异常 - 这是Task
中您忽略的信息的一部分(使用Action
代替Func<Task>
)。
总而言之,您刚刚编写了一个更加混淆的版本:
_branchClient.ReadOneItemAsync(AppSettings.BranchId, _initCts.Token);
.NET构造函数本质上是同步的。它们不应该在您使用异步代码的情况下做任何事情 - 并且在构造函数(或构造函数调用的方法)中尽可能少地做它是个好主意。如果您需要复杂的操作,异步代码,I / O,大量的CPU工作...使用静态方法。因为你正在运行异步代码,所以让它返回Task<AppStateModel>
,在整个异步流程中正确拟合。
另请注意,在旧的.NET运行时中,不会被吞噬。假设没有同步上下文,异常仍然在后台线程上抛出(其中发布了异步操作的延续) - 并且线程池线程上未处理的异常的默认值曾经是“降低整个应用程序”。当Task
对象被最终确定时会发生这种情况,因此与您的所有程序逻辑分离,就您所知,它几乎是随机的。毕竟,你还能做些什么 - 没有可以观察到异常的好地方,那里唯一的根本就是“我不关心这项任务会发生什么”。但考虑到确保正确观察和处理每个异常的复杂程度,默认值已更改为“忽略未观察到的异常”。
答案 1 :(得分:2)
让我们解构这里发生的事情:你有一个指向匿名async void
方法的Action委托。 async void
是什么意思?这意味着该方法实际上返回一个封装异步逻辑的Task
。
这意味着当您调用loadBranch
时,它会执行异步方法。当异步方法命中await
时,它会返回一个Task
对象,允许您等待它,添加延续或其他任何内容。但是由于你没有明确的Task变量来捕获它,你只需让你的构造函数超出范围,而没有任何代码处理Task的延续。这意味着当任务抛出时,ctor已经退出。