在异步操作的任务中处理异常的位置
本文关键字:异常 位置 处理 任务 异步操作 | 更新日期: 2023-09-27 17:58:02
示例代码如下:
Action action = async () =>
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
};
Task.Run(action);
Console.ReadKey();
在哪里处理异常?
代码有两个错误。
首先,它使用了一个async void
委托,它可以防止异常正常工作(有关避免async void
的更多信息,请参阅我在MSDN上关于异步最佳实践的文章)。它应该使用Func<Task>
而不是Action
(有关异步友好委托类型的更多信息,请参阅我关于同步和异步委托类型的博客文章):
Func<Task> action = async () =>
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
};
第二个错误是它在线程池上运行委托时使用了即发即弃。即发即弃中的"忘记"部分意味着"忽略所有例外情况"。为了正确地传播异常,应该等待从Task.Run
返回的任务(有关await
如何处理任务的更多信息,请参阅我关于async和await的博客文章):
await Task.Run(action);
您可以在任务本身内部或由调用方在外部处理它,只需注意任务上的等待即可。运行时,这可以确保您捕获异常,而不是使其静默死亡。`
Func<Task> action = async () =>
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
};
try
{
await Task.Run(action);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
此外,请查看这篇关于异步/等待异常处理的差异的文章
由于您正在使用Task.Run(action);
,并且您没有等待返回的任务对象,因此异常会在另一个线程中抛出,并且您无法在调用方线程中处理它(例如使用ContinueWith
);
然后您需要处理Action委托内部的异常:
Action action = async () =>
{
try
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
}
catch(Exception ex)
{
// do something
}
};
人们通常认为在使用异步等待时会涉及到几个线程,而事实上并不是,除非您指定这样做。
阅读EricLippert关于异步等待搜索的文章。
他将异步等待比作厨师:烤面包时,他可以等到面包烤好后再煮开水泡茶和鸡蛋。如果他开始烧水,然后回头看看面包是否烤好了,效率会更高。
同样的情况也发生在您的代码中。在等待任务期间。延迟您的线程不会开始等待。相反,它会向上调用堆栈,查看其中一个调用方(所有调用方都必须是异步的!)是否没有在等待,因此可以在没有调用结果的情况下继续处理。过了一段时间,它返回查看Task.Delay是否完成,并继续抛出异常的下一个语句。
请注意,在这个场景中,只涉及一个线程。异常捕获与所有其他异常捕获一样完成。尽管catcher可以检查调用堆栈,但他不确定哪些代码被执行,哪些没有执行。在这方面,与非异步等待没有太大区别