在异步操作的任务中,异常应该在哪里处理?

3
样例代码如下:
Action action = async () =>
{
    Console.WriteLine("Action start...");
    await Task.Delay(1000);
    throw new Exception("Exception from an async action");
};

Task.Run(action);

Console.ReadKey(); 

异常处理应该放在哪里?

4个回答

5

这段代码有两个问题。

首先,它使用了一个 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");
};

第二个问题是在线程池上运行委托时使用了“fire-and-forget”。 “fire-and-forget”中的“forget”意味着“忽略所有异常”。 为了正确传播异常,应该等待从Task.Run返回的任务(有关await如何与任务一起使用的更多信息,请参见我的有关异步和等待的博客文章):
await Task.Run(action);

是的,你说得对。实际上,在我发帖之前我已经阅读了你的文章。问题是我需要编写一个方法来接受一个Action实例作为参数,并通过任务执行它。我还没有找到避免未处理异常导致代码崩溃的方法。 - Wang
@Wang:如果类型必须是“Action”(同步),而实现必须是异步的话——这是非常罕见的情况——那么你只能在 lambda 表达式中捕获所有异常。 - Stephen Cleary

2
你可以在任务本身内部或由调用者外部处理它,只要注意在Task.Run上等待,这样可以确保你捕获异常而不是让它静默死亡。
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(); 

此外,请参考此帖子,了解异步/等待异常处理的区别。


但是原始问题没有等待“任务”。 - Zein Makki
1
他没有使用try/catch,这就是为什么我们要为他提供答案的原因,对吧? - VidasV
1
你执行了你的代码片段吗?如果这么容易解决,我就不会发布它了。 - Wang
你说得完全正确... 抱歉误导了你。像Stephen建议的那样,尝试用Func<Task>替换Action。我也会修改我的代码。 - VidasV

0

很多人认为在使用async-await时会涉及多个线程,但实际上除非你明确指定这样做,否则不会。

请阅读Eric Lippert关于async-await的文章,在中间某处搜索async-await。

他将async-await与烹饪比较:当烤面包时,他可以等待面包烤好后再煮茶和蛋。如果他先开始煮水,然后回头看面包是否烤好,效率会更高。

在你的代码中也是如此。在await task.Delay期间,你的线程并不会开始等待。相反,它会向上调用堆栈以查看是否有一个调用者(他们都必须是异步的!)没有等待,因此可以继续处理而不必等待其调用的结果。一段时间后,它会返回并查看Task.Delay是否完成,并继续抛出下一个语句中的异常。

请注意,在这种情况下只涉及一个线程。异常捕获与所有其他异常捕获一样完成。尽管捕获器可以检查调用堆栈,但他不确定执行哪些代码片段和哪些不执行。在这方面,与非异步等待没有太大的区别。

0

由于您正在使用 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
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接