(这是对这个问题的一次新尝试,现在更好地证明了这个问题。)
假设我们有一个错误的任务 ( 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;
这会抛出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 客户端无法检测到原始任务和代理任务之间的差异,这一点很重要。