如何使用TaskCompletionSource.SetException保留等待行为?

usr*_*usr 16 .net c# task-parallel-library async-await taskcompletionsource

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

假设我们有一个错误的任务(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但是,抛出,这是没有记录的行为.

当我们想要创建代理任务并设置它的异常时,这就成了一个问题:

var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception);
await proxyTcs.Task;
Run Code Online (Sandbox Code Playgroud)

这将引发AggregateException而await 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.
}
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*eet 8

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

不,那是行为,当第一嵌套异常是一个AggregateException.

基本上,当你打电话时TaskCompletionSource.SetException(Exception),它将该异常包装在一个AggregateException.

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

proxyTcs.SetException(faultedTask.Exception.InnerExceptions);
Run Code Online (Sandbox Code Playgroud)