如何使用 TaskCompletionSource.SetException 保留等待行为?
How to preserve await behavior with TaskCompletionSource.SetException?
(这是对这个问题的新尝试,现在更好地证明了这个问题。)
假设我们有一个错误的任务 (var faultedTask = Task.Run(() => { throw new Exception("test"); });
),我们等待它。 await
将解压 AggregateException
并抛出底层异常。它会抛出 faultedTask.Exception.InnerExceptions.First()
.
根据 ThrowForNonSuccess
的源代码,它将通过执行任何存储的 ExceptionDispatchInfo
来执行此操作,大概是为了保留良好的堆栈跟踪。如果没有 ExceptionDispatchInfo
.
,它不会解压 AggregateException
这个事实本身就让我感到惊讶,因为文档指出第一个异常是 always 抛出: 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 客户端无法检测到原始任务和代理任务之间的差异。
It turns out that await can throw AggregateException, though, which is not documented behavior.
不,这是第一个嵌套异常 是 和 AggregateException
.
时的行为
基本上,当您调用 TaskCompletionSource.SetException(Exception)
时,它会将异常包装在 AggregateException
.
中
如果你想保留 多个 异常,只需使用 SetException
的重载,它接受 IEnumerable<Exception>
:
proxyTcs.SetException(faultedTask.Exception.InnerExceptions);
(这是对这个问题的新尝试,现在更好地证明了这个问题。)
假设我们有一个错误的任务 (var faultedTask = Task.Run(() => { throw new Exception("test"); });
),我们等待它。 await
将解压 AggregateException
并抛出底层异常。它会抛出 faultedTask.Exception.InnerExceptions.First()
.
根据 ThrowForNonSuccess
的源代码,它将通过执行任何存储的 ExceptionDispatchInfo
来执行此操作,大概是为了保留良好的堆栈跟踪。如果没有 ExceptionDispatchInfo
.
AggregateException
这个事实本身就让我感到惊讶,因为文档指出第一个异常是 always 抛出: 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 客户端无法检测到原始任务和代理任务之间的差异。
It turns out that await can throw AggregateException, though, which is not documented behavior.
不,这是第一个嵌套异常 是 和 AggregateException
.
基本上,当您调用 TaskCompletionSource.SetException(Exception)
时,它会将异常包装在 AggregateException
.
如果你想保留 多个 异常,只需使用 SetException
的重载,它接受 IEnumerable<Exception>
:
proxyTcs.SetException(faultedTask.Exception.InnerExceptions);