捕获asynchronous方法引发的exception

使用Microsoft for .NET的async ctp,是否有可能捕获调用方法中的asynchronous方法引发的exception?

public async void Foo() { 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 */ } public void DoFoo() { try { Foo(); } catch (ProtocolException ex) { /* The exception will never be caught Instead when in debug mode, VS2010 will warn and continue when deployed the app will simply crash. */ } } 

所以基本上我想从asynchronous代码exception冒泡到我的调用代码,如果甚至可能的话。

看起来有点奇怪,但是,这个exception会冒泡到调用代码 – 但是只有当您awaitWait()调用Foo

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

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

这个解释http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions是相当不错的; – 它讨论了编译器实现这个魔术的步骤。

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

如果您可以更改方法签名,则会打开一个更简单的解决scheme – 更改Foo()以便返回typesTask ,然后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 } } 

你的代码不是你的想法。 方法开始等待asynchronous结果后,asynchronous方法将立即返回。 为了看看发生了什么,增加一些跟踪来了解线程交互是如何进行的。

在下面的例子中,我做了

  • 创build4个任务
  • 每个任务将asynchronous增加一个数字并返回递增的数字
  • 当asynchronous结果已经到达时,它被追踪。

     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方法在线程2880上完成,而另一个线程上只有一个计算已经开始。 如果围绕await方法放置一个try / catch,你可以用通常的方法“捕捉”exception,尽pipe当计算任务完成并且执行了contiuation之后,你的代码将在另一个线程上执行。

计算方法自动追踪抛出的exception,因为我使用ApiChange工具中的ApiChange.Api.dll 。 跟踪和reflection器有助于了解正在发生的事情。 为了摆脱线程,您可以创build自己的GetAwaiter BeginAwait和EndAwait版本,并且不包含任务,但是例如在您自己的扩展方法中使用Lazy和trace。 那么你将会更好地理解编译器和TPL的function。

现在你看到没有办法进入try / catch你的exception,因为没有任何exception传播的栈帧。 在启动asynchronous操作之后,您的代码可能会完全不同。 它可能会调用Thread.Sleep甚至终止。 只要有一个前台线程离开,你的应用程序就会继续执行asynchronous任务。


asynchronous操作完成后,可以在asynchronous方法内处理exception,并调用回UI线程。 推荐的方法是使用TaskScheduler.FromSynchronizationContext 。 如果你有一个UI线程,并且它不是非常忙于其他事情,这只会工作。

你的,阿洛伊斯·克劳斯

同样重要的是要注意,如果你在asynchronous方法上有一个void返回types,那么你将失去exception的时序堆栈跟踪。 我会build议返回任务如下。 让整个debugging变得更容易

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

exception可以在asynchronous函数中捕获。

 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(); } 

这个博客解释您的问题整齐的asynchronous最佳实践 。

它的主旨是你不应该使用void作为asynchronous方法的返回,除非它是一个asynchronous事件处理程序,这是不好的做法,因为它不允许exception被捕获;-)。

最佳做法是将返回types更改为任务。 另外,尽量编写asynchronous低谷,使每个asynchronous方法调用,并从asynchronous方法调用。 除了控制台中的Main方法,它不能是asynchronous的。

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

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