在异步操作的任务中在哪里处理异常?
Where to handle an exception in a task of async action?
示例代码是这样的:
Action action = async () =>
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
};
Task.Run(action);
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
}
};
您可以在任务本身内部或调用者外部处理它,只需注意 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();
此外,检查此 post 关于 async/await 异常处理的差异
人们常常认为使用 async-await 时会涉及多个线程,但实际上并非如此,除非您指定这样做。
阅读 Eric Lippert about async-await 在中间某处搜索异步等待。
他把 async await 比作一个厨师:在烤面包时,他可以等到面包烤好,然后再开水泡茶和鸡蛋。如果他开始烧水,然后回头看面包是否烤好,效率会更高。
您的代码中也会发生同样的情况。在 await task.Delay 期间,您的线程不会开始等待。取而代之的是,它会在其调用堆栈中上升,以查看其中一个调用者(他们都必须是异步的!)是否没有在等待,因此可以在没有调用结果的情况下继续处理。过了一会儿,它 returns 查看 Task.Delay 是否完成并继续下一个抛出异常的语句。
请注意,在这种情况下,只涉及一个线程。异常捕获与所有其他异常捕获一样完成。虽然捕手可以检查调用堆栈,但他不确定哪些代码段被执行了,哪些没有。在这方面与非异步等待没有太大区别
代码有两处错误。
首先,它使用了一个 async void
委托,它可以防止异常正常工作(有关避免 async void
的更多信息,请参阅 my MSDN article on async best practices). It should be using Func<Task>
instead of Action
(for more information on async-friendly delegate types, see my blog post on synchronous and asynchronous delegate types):
Func<Task> action = async () =>
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
};
第二个错误是当 运行 线程池上的委托时它使用了即发即弃。 "fire-and-forget" 的 "forget" 部分表示 "ignore all exceptions"。要正确传播异常,应等待从 Task.Run
返回的任务(有关 await
如何处理任务的更多信息,请参阅我的 blog post on async and await):
await Task.Run(action);
示例代码是这样的:
Action action = async () =>
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
};
Task.Run(action);
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
}
};
您可以在任务本身内部或调用者外部处理它,只需注意 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();
此外,检查此 post 关于 async/await 异常处理的差异
人们常常认为使用 async-await 时会涉及多个线程,但实际上并非如此,除非您指定这样做。
阅读 Eric Lippert about async-await 在中间某处搜索异步等待。
他把 async await 比作一个厨师:在烤面包时,他可以等到面包烤好,然后再开水泡茶和鸡蛋。如果他开始烧水,然后回头看面包是否烤好,效率会更高。
您的代码中也会发生同样的情况。在 await task.Delay 期间,您的线程不会开始等待。取而代之的是,它会在其调用堆栈中上升,以查看其中一个调用者(他们都必须是异步的!)是否没有在等待,因此可以在没有调用结果的情况下继续处理。过了一会儿,它 returns 查看 Task.Delay 是否完成并继续下一个抛出异常的语句。
请注意,在这种情况下,只涉及一个线程。异常捕获与所有其他异常捕获一样完成。虽然捕手可以检查调用堆栈,但他不确定哪些代码段被执行了,哪些没有。在这方面与非异步等待没有太大区别
代码有两处错误。
首先,它使用了一个 async void
委托,它可以防止异常正常工作(有关避免 async void
的更多信息,请参阅 my MSDN article on async best practices). It should be using Func<Task>
instead of Action
(for more information on async-friendly delegate types, see my blog post on synchronous and asynchronous delegate types):
Func<Task> action = async () =>
{
Console.WriteLine("Action start...");
await Task.Delay(1000);
throw new Exception("Exception from an async action");
};
第二个错误是当 运行 线程池上的委托时它使用了即发即弃。 "fire-and-forget" 的 "forget" 部分表示 "ignore all exceptions"。要正确传播异常,应等待从 Task.Run
返回的任务(有关 await
如何处理任务的更多信息,请参阅我的 blog post on async and await):
await Task.Run(action);