C# 如何使用TaskCompletionSource.SetException保留等待行为?
(这是对这个问题的新尝试,现在更好地说明了这个问题。) 假设我们有一个出错的任务(C# 如何使用TaskCompletionSource.SetException保留等待行为?,c#,.net,async-await,task-parallel-library,taskcompletionsource,C#,.net,Async Await,Task Parallel Library,Taskcompletionsource,(这是对这个问题的新尝试,现在更好地说明了这个问题。) 假设我们有一个出错的任务(var faultedTask=task.Run(()=>{throw new Exception(“test”);})),我们等待它await将解压aggregateeexception并引发底层异常。它将抛出faultedTask.Exception.InnerExceptions.First() 根据ThrowForNonSuccess的源代码,它将通过执行任何存储的ExceptionDispatchInfo
var faultedTask=task.Run(()=>{throw new Exception(“test”);})
),我们等待它await
将解压aggregateeexception
并引发底层异常。它将抛出faultedTask.Exception.InnerExceptions.First()
根据ThrowForNonSuccess的源代码,它将通过执行任何存储的ExceptionDispatchInfo
来实现这一点,以保留良好的堆栈跟踪。如果没有例外DispatchInfo
,它将不会解压缩聚合异常
这一事实本身就让我感到惊讶,因为文档中说明了第一个异常总是被抛出的:事实证明,await
可以抛出aggregateeexception
,但这不是文档化的行为
当我们要创建代理任务并设置其异常时,这将成为一个问题:
var proxyTcs = new TaskCompletionSource<object>();
proxyTcs.SetException(faultedTask.Exception);
await proxyTcs.Task;
这个问题的动机是我试图构造一个函数,将一个任务的结果复制到TaskCompletionSource
。这是一个帮助函数,在编写任务组合器函数时经常使用。重要的是,API客户端无法检测原始任务和代理任务之间的差异
事实证明,Wait可以抛出AggregateException,但这并不是有文档记录的行为
不,这是当第一个嵌套异常是AggregateException
时的行为
基本上,当您调用TaskCompletionSource.SetException(Exception)
时,它会将该异常包装在一个aggregateeexception
中
如果要保留多个异常,只需使用SetException
的重载,它接受IEnumerable
:
阅读来自50k+代表用户的新问题总是很有趣的,90%的时间我都会说“哦,我也很想知道”并最终喜欢这个问题如果你想了解异常的wait
语义,为什么不直接转到源代码()复制任务等待者所做的事情?@StephenCleary mine现在正是本着这种精神进行建模的。同时,我也复习了乔恩的公共守则。显然,除了我,每个人都知道使用正确的过载。正如我刚才测试的那样,让TPL代码的每一个细微差别都正确是一门黑色艺术。更倾向于使用更干净的SetException方法,尽管这两种方法都可以使用堆栈跟踪,但“展开”中有一个无用的部分。
[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.
}
proxyTcs.SetException(faultedTask.Exception.InnerExceptions);