捕获由异步void方法抛出的异常

使用微软的。net异步CTP, 是否有可能在调用方法中捕获异步方法抛出的异常?< / p >

public async void Foo()
{
var x = await DoSomethingAsync();


/* Handle the result, but sometimes an exception might be thrown.
For example, DoSomethingAsync gets data from the network
and the data is invalid... a ProtocolException might be thrown. */
}


public void DoFoo()
{
try
{
Foo();
}
catch (ProtocolException ex)
{
/* The exception will never be caught.
Instead when in debug mode, VS2010 will warn and continue.
The deployed the app will simply crash. */
}
}
所以基本上我想从异步代码的异常冒泡到我的调用代码 如果这是可能的,

284100 次浏览

这读起来有点奇怪,但是是的,异常会冒泡到调用代码-但只有如果你__ABC0或__ABC1调用Foo

public async Task Foo()
{
var x = await DoSomethingAsync();
}


public async void DoFoo()
{
try
{
await Foo();
}
catch (ProtocolException ex)
{
// The exception will be caught because you've awaited
// the call in an async method.
}
}


//or//


public void DoFoo()
{
try
{
Foo().Wait();
}
catch (ProtocolException ex)
{
/* The exception will be caught because you've
waited for the completion of the call. */
}
}

正如Stephen Cleary在异步/等待——异步编程的最佳实践中所写:

异步void方法具有不同的错误处理语义。当async Task或async Task方法抛出异常时,将捕获该异常并将其放置在Task对象上。对于异步void方法,没有Task对象,因此异步void方法抛出的任何异常将直接在异步void方法启动时激活的SynchronizationContext上引发。

注意,如果. net决定同步执行你的方法,使用Wait()可能会导致应用程序阻塞。

这个解释http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions非常好——它讨论了编译器实现这个魔法所采取的步骤。

可以在async函数中捕获异常。

public async void Foo()
{
try
{
var x = await DoSomethingAsync();
/* Handle the result, but sometimes an exception might be thrown
For example, DoSomethingAsync get's data from the network
and the data is invalid... a ProtocolException might be thrown */
}
catch (ProtocolException ex)
{
/* The exception will be caught here */
}
}


public void DoFoo()
{
Foo();
}

你的代码并没有像你想象的那样。异步方法在方法开始等待异步结果后立即返回。使用跟踪来调查代码的实际行为是很有见地的。

下面的代码执行以下操作:

  • 创建4个任务
  • 每个任务将异步递增一个数字并返回递增后的数字
  • 当异步结果到达时,它将被跟踪。

,

static TypeHashes _type = new TypeHashes(typeof(Program));
private void Run()
{
TracerConfig.Reset("debugoutput");


using (Tracer t = new Tracer(_type, "Run"))
{
for (int i = 0; i < 4; i++)
{
DoSomeThingAsync(i);
}
}
Application.Run();  // Start window message pump to prevent termination
}




private async void DoSomeThingAsync(int i)
{
using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
{
t.Info("Hi in DoSomething {0}",i);
try
{
int result = await Calculate(i);
t.Info("Got async result: {0}", result);
}
catch (ArgumentException ex)
{
t.Error("Got argument exception: {0}", ex);
}
}
}


Task<int> Calculate(int i)
{
var t = new Task<int>(() =>
{
using (Tracer t2 = new Tracer(_type, "Calculate"))
{
if( i % 2 == 0 )
throw new ArgumentException(String.Format("Even argument {0}", i));
return i++;
}
});
t.Start();
return t;
}

当你观察痕迹的时候

22:25:12.649  02172/02820 {          AsyncTest.Program.Run
22:25:12.656  02172/02820 {          AsyncTest.Program.DoSomeThingAsync
22:25:12.657  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0
22:25:12.658  02172/05220 {          AsyncTest.Program.Calculate
22:25:12.659  02172/02820 {          AsyncTest.Program.DoSomeThingAsync
22:25:12.659  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1
22:25:12.660  02172/02756 {          AsyncTest.Program.Calculate
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3
22:25:12.664  02172/02756          } AsyncTest.Program.Calculate Duration 4ms
22:25:12.666  02172/02820          } AsyncTest.Program.Run Duration 17ms  ---- Run has completed. The async methods are now scheduled on different threads.
22:25:12.667  02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1
22:25:12.667  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 8ms
22:25:12.667  02172/02756 {          AsyncTest.Program.Calculate
22:25:12.665  02172/05220 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
22:25:12.668  02172/02756 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
22:25:12.724  02172/05220          } AsyncTest.Program.Calculate Duration 66ms
22:25:12.724  02172/02756          } AsyncTest.Program.Calculate Duration 57ms
22:25:12.725  02172/05220 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0


Server stack trace:
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()


Exception rethrown at [0]:
at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()
at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()
at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106
22:25:12.725  02172/02756 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2


Server stack trace:
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()


Exception rethrown at [0]:
at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()
at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()
at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 70ms
22:25:12.726  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 64ms
22:25:12.726  02172/05220 {          AsyncTest.Program.Calculate
22:25:12.726  02172/05220          } AsyncTest.Program.Calculate Duration 0ms
22:25:12.726  02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 64ms

您将注意到Run方法在线程2820上完成,而只有一个子线程完成了(2756)。如果你在await方法周围放置了try/catch,你可以以通常的方式“捕获”异常,尽管你的代码在计算任务完成时在另一个线程上执行,并且执行了你的延续。

计算方法自动跟踪抛出的异常,因为我确实使用了ApiChange工具中的ApiChange.Api.dll。 跟踪和反射器有助于理解发生了什么。为了摆脱线程,你可以创建自己版本的GetAwaiter BeginAwait和EndAwait,并在你自己的扩展方法中包装一个Lazy和trace而不是一个任务。这样你就能更好地理解编译器和TPL做什么

现在您可以看到,没有办法返回try/catch异常,因为没有堆栈帧可供任何异常从其中传播。在您初始化异步操作之后,您的代码可能会执行完全不同的操作。它可能调用Thread。睡眠甚至终止。只要还有一个前台线程,你的应用程序就会继续执行异步任务。


您可以在异步操作完成并回调到UI线程后在异步方法内部处理异常。推荐的方法是使用TaskScheduler。FromSynchronizationContext。这只在你有一个UI线程并且它不忙于其他事情时才有效。

没有捕获异常的原因是因为Foo()方法有一个void返回类型,所以当await被调用时,它只是返回。由于DoFoo()不等待Foo的完成,异常处理程序不能被使用。

这就提供了一个更简单的解决方案,如果你可以改变方法签名——改变Foo(),使它返回类型Task,然后DoFoo()可以await Foo(),如下所示:

public async Task Foo() {
var x = await DoSomethingThatThrows();
}


public async void DoFoo() {
try {
await Foo();
} catch (ProtocolException ex) {
// This will catch exceptions from DoSomethingThatThrows
}
}

同样重要的是要注意,如果你在异步方法上有一个void返回类型,你将失去异常的按时间顺序的堆栈跟踪。我建议按如下方式返回Task。这会让调试变得更容易。

public async Task DoFoo()
{
try
{
return await Foo();
}
catch (ProtocolException ex)
{
/* Exception with chronological stack trace */
}
}

这个博客清楚地解释了你的问题。

它的要点是你不应该使用void作为异步方法的返回值,除非它是一个异步事件处理程序,这是一个坏习惯,因为它不允许捕获异常;-)。

最佳实践是将返回类型更改为Task。 此外,尽量编写异步代码,使每个异步方法调用和被异步方法调用。除了控制台中的Main方法,它不能是异步的(c# 7.1之前)

在使用GUI和ASP时,您将遇到死锁。NET应用程序,如果您忽略了这个最佳实践。发生死锁是因为这些应用程序运行在只允许一个线程的上下文中,并且不会将其放弃给异步线程。这意味着GUI同步地等待返回,而async方法等待上下文:死锁。

这种行为不会发生在控制台应用程序中,因为它运行在带有线程池的上下文中。async方法将在另一个被调度的线程上返回。这就是为什么测试控制台应用程序可以工作,但相同的调用在其他应用程序中会死锁…