在异步操作的任务中处理异常的位置

本文关键字:异常 位置 处理 任务 异步操作 | 更新日期: 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可以检查调用堆栈,但他不确定哪些代码被执行,哪些没有执行。在这方面,与非异步等待没有太大区别