(这是对该问题的新尝试,现在更好地展示了该问题。)
假设我们有一个出现故障的任务 (var faultedTask = Task.Run(() => { throw new Exception("test"); });
) 并且我们等待它。 await
将解包 AggregateException
并抛出底层异常。它将抛出 faultedTask.Exception.InnerExceptions.First()
。
根据 ThrowForNonSuccess
的源代码,它将通过执行任何存储的 ExceptionDispatchInfo
来执行此操作,可能是为了保留良好的堆栈跟踪。如果没有 ExceptionDispatchInfo
,它将不会解包 AggregateException
。
仅此事实就让我感到惊讶,因为文档说明第一个异常总是被抛出: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;
这会抛出 AggregateException
异常,而 await faultedTask;
会抛出测试异常。
我该如何创建一个代理任务,使其可以随意完成并镜像原始任务的异常行为?
原始行为是:
await
将抛出第一个内部异常。- 所有异常仍然可通过
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客户端不能检测到原始任务和代理任务之间的差异。
await
语义,为什么不直接去源代码(http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs,ca9850c71672bd54)并复制`TaskAwaiter`所做的呢? - Kirill Shlenskiy