捕获异步委托中的异常

时间:2012-12-31 13:22:34

标签: c# async-await

我有这段代码:

    public async Task AsyncMethod()
    {
        await Task.Factory.StartNew(() =>
        {
            throw new Exception();
        });
    }

    public ActionResult Index()
    {
        var t1 = Task.Factory.StartNew(() => { throw new Exception(); });
        var t2 = Task.Factory.StartNew(() => { throw new Exception();});
        var t3 = Task.Factory.StartNew(async () => { await AsyncMethod(); });

        try 
        {
            Task.WaitAll(t1, t2, t3);
        }
        catch (AggregateException ex)
        {
            var count1 = ex.InnerExceptions.Count;
            var count2 = ex.Flatten().InnerExceptions.Count;

            throw;
        }

        return View();
    }

我想了解为什么count1和count2变量是2而不是3,我怎样才能在AsyncMethod中得到第三个异常?

2 个答案:

答案 0 :(得分:3)

更改此

    var t3 = Task.Factory.StartNew(async () => { await AsyncMethod(); });

到此:

    var t3 = AsyncMethod();

您已经拥有任务。无需通过在线程池上执行它来包装它。

通过包装它,您将已经拥有的Task(最终将会出错)转换为永不发生故障的Task<Task>(仅Task.Result个故障)。当然WaitAll对外部任务进行操作,因此它永远不会看到异常。

使用调试器逐步执行此类代码以检查所有变量的运行时值是一种很好的技术。通过查看代码比这更容易注意到这个错误(“看起来”对我来说已经足够了,因为我对此有经验 - 但如果我没有经验,我将需要运行此代码和调试它)。

答案 1 :(得分:3)

  

我想了解为什么count1和count2变量是2而不是3,我怎样才能在AsyncMethod中得到第三个异常?

Task.Factory.StartNew返回基本Task。如果您将async委托传递给它,则返回的Task仅表示async方法的开头(直到它向其调用方产生的点)。

您应该将Task.Runasync代码一起使用。 Task.Run会为Task委托创建async包装,因此从Task返回的Task.Run代表整个async方法。

Stephen Toub有an excellent blog post detailing the differences between Task.Run and Task.Factory.StartNew

另外,正如我们所提到的,每当你在GUI或ASP.NET上下文中阻塞Task而不是await时,就会遇到死锁问题。我有a blog post that goes into detail about this deadlock problem。您应该使用await Task.WhenAll代替Task.WaitAll

所以,这里是你的代码应用了两个更改:

public async Task AsyncMethod()
{
    await Task.Run(() =>
    {
        throw new Exception();
    });
}

public async Task<ActionResult> Index()
{
    var t1 = Task.Run(() => { throw new Exception(); });
    var t2 = Task.Run(() => { throw new Exception();});
    var t3 = Task.Run(async () => { await AsyncMethod(); });

    try 
    {
        await Task.WhenAll(t1, t2, t3);
    }
    catch (Exception)
    {
        var ex1 = t1.Exception.InnerException;
        var ex2 = t2.Exception.InnerException;
        var ex3 = t3.Exception.InnerException;

        throw;
    }

    return View();
}