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

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. */
    }
}

基本上我想让异步代码中的异常冒泡到我的调用代码中 如果有可能的话。


当前回答

同样重要的是要注意,如果你在异步方法上有一个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方法将在另一个被调度的线程上返回。这就是为什么测试控制台应用程序可以工作,但相同的调用在其他应用程序中会死锁…

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

这读起来有点奇怪,但是是的,异常会冒泡到调用代码中-但只有当您等待或Wait()对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在Async/Await -异步编程的最佳实践中所写的:

异步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非常好——它讨论了编译器实现这个魔法所采取的步骤。

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

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

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

这打开了一个更简单的解决方案,如果你可以改变方法签名-改变Foo(),使它返回类型任务,然后DoFoo()可以等待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
    }
}