17

(这是对这个问题的一次新尝试,现在更好地证明了这个问题。)

假设我们有一个错误的任务 ( var faultedTask = Task.Run(() => { throw new Exception("test"); });) 并且我们等待它。await将解包AggregateException并抛出底层异常。它会抛出faultedTask.Exception.InnerExceptions.First().

根据源代码,ThrowForNonSuccess它将通过执行任何存储来做到这一点,ExceptionDispatchInfo大概是为了保留漂亮的堆栈跟踪。AggregateException如果没有,它不会解包ExceptionDispatchInfo

仅这一事实就让我感到惊讶,因为文档指出总是抛出第一个异常:https://msdn.microsoft.com/en-us/library/hh156528.aspx?f=255&MSPPError=-2147217396事实证明await可以AggregateException但是,throw没有记录在案的行为。

当我们要创建代理任务并将其设置为异常时,这会成为一个问题:

var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception);
await proxyTcs.Task;

这会抛出AggregateExceptionawait faultedTask;会抛出测试异常。

如何创建一个我可以随意完成的代理任务,并反映原始任务的异常行为?

原来的行为是:

  1. await将抛出第一个内部异常。
  2. 所有例外情况仍可通过Task.Exception.InnerExceptions. (这个问题的早期版本省略了这个要求。)

这是一个总结发现的测试:

[TestMethod]
public void ExceptionAwait()
{
    ExceptionAwaitAsync().Wait();
}

static async Task ExceptionAwaitAsync()
{
    //Task has multiple exceptions.
    var faultedTask = Task.WhenAll(Task.Run(() => { throw new Exception("test"); }), Task.Run(() => { throw new Exception("test"); }));

    try
    {
        await faultedTask;
        Assert.Fail();
    }
    catch (Exception ex)
    {
        Assert.IsTrue(ex.Message == "test"); //Works.
    }

    Assert.IsTrue(faultedTask.Exception.InnerExceptions.Count == 2); //Works.

    //Both attempts will fail. Uncomment attempt 1 to try the second one.
    await Attempt1(faultedTask);
    await Attempt2(faultedTask);
}

static async Task Attempt1(Task faultedTask)
{
    var proxyTcs = new TaskCompletionSource<object>();
    proxyTcs.SetException(faultedTask.Exception);

    try
    {
        await proxyTcs.Task;
        Assert.Fail();
    }
    catch (Exception ex)
    {
        Assert.IsTrue(ex.Message == "test"); //Fails.
    }
}

static async Task Attempt2(Task faultedTask)
{
    var proxyTcs = new TaskCompletionSource<object>();
    proxyTcs.SetException(faultedTask.Exception.InnerExceptions.First());

    try
    {
        await proxyTcs.Task;
        Assert.Fail();
    }
    catch (Exception ex)
    {
        Assert.IsTrue(ex.Message == "test"); //Works.
    }

    Assert.IsTrue(proxyTcs.Task.Exception.InnerExceptions.Count == 2); //Fails. Should preserve both exceptions.
}

这个问题的动机是我正在尝试构建一个函数,它将一个任务的结果复制到TaskCompletionSource. 这是编写任务组合函数时经常使用的辅助函数。API 客户端无法检测到原始任务和代理任务之间的差异,这一点很重要。

4

1 回答 1

10

事实证明,await 可以抛出 AggregateException,但是,这不是记录的行为。

不,这是第一个嵌套异常AggregateException.

基本上,当您调用 时TaskCompletionSource.SetException(Exception),它会将该异常包装在一个AggregateException.

如果要保留多个异常,只需使用SetException接受的重载IEnumerable<Exception>

proxyTcs.SetException(faultedTask.Exception.InnerExceptions);
于 2016-02-15T16:03:55.100 回答