一个任务失败时,是否可以从Task.WhenAll获得成功的结果? [英] Is it possible to get successful results from a Task.WhenAll when one of the tasks fails?
问题描述
给出以下内容:
var tPass1 = Task.FromResult(1);
var tFail1 = Task.FromException<int>(new ArgumentException("fail1"));
var tFail2 = Task.FromException<int>(new ArgumentException("fail2"));
var task = Task.WhenAll(tPass1, tFail1, tFail2);
task.Wait();
对task.Wait()的调用将抛出一个AggregateException
,其内部异常包含fail1
和fail2
异常.但是如何获得tPass1
成功的结果?
the call to task.Wait() throws an AggregateException
, whose inner exceptions contain the fail1
and fail2
exceptions. But how can I access the tPass1
successful result?
这可能吗?
我知道在WhenAll
完成之后我可以通过tPass1.Result
从单个任务中获得结果,但是有一种方法可以将它们放入数组中,从而不必手动跟踪所有进给的东西进入WhenAll
?
I'm aware that I can get the result from the individual task after the WhenAll
has finished, via tPass1.Result
however is there a way to get them in an array to avoid having to manually track all the things feeding into the WhenAll
?
推荐答案
任务失败时,我们将无法访问其 Result
属性,因为它会抛出.因此,要获得部分成功完成的WhenAll
任务的结果,我们必须确保该任务将成功完成.然后,问题就变成了如何处理失败的内部任务.吞下它们可能不是一个好主意.至少我们要记录它们.这是一个替代性WhenAll
的实现,该替代性不会抛出任何异常,但是会在 ValueTuple
结构.
When a task fails we cannot access its Result
property because it throws. So to have the results of a partially successful WhenAll
task, we must ensure that the task will complete successfully. The problem then becomes what to do with the exceptions of the failed internal tasks. Swallowing them is probably not a good idea. At least we would like to log them. Here is an implementation of an alternative WhenAll
that never throws, but returns both the results and the exceptions in a ValueTuple
struct.
public static Task<(T[] Results, Exception[] Exceptions)> WhenAllEx<T>(params Task<T>[] tasks)
{
return Task.WhenAll(tasks).ContinueWith(_ => // return a continuation of WhenAll
{
var results = tasks
.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToArray();
var aggregateExceptions = tasks
.Where(t => t.IsFaulted)
.Select(t => t.Exception) // The Exception is of type AggregateException
.ToArray();
var exceptions = new AggregateException(aggregateExceptions).Flatten()
.InnerExceptions.ToArray(); // Trick to flatten the hierarchy of AggregateExceptions
return (results, exceptions);
}, TaskContinuationOptions.ExecuteSynchronously);
}
用法示例:
var tPass1 = Task.FromResult(1);
var tFail1 = Task.FromException<int>(new ArgumentException("fail1"));
var tFail2 = Task.FromException<int>(new ArgumentException("fail2"));
var task = WhenAllEx(tPass1, tFail1, tFail2);
task.Wait();
Console.WriteLine($"Status: {task.Status}");
Console.WriteLine($"Results: {String.Join(", ", task.Result.Results)}");
Console.WriteLine($"Exceptions: {String.Join(", ", task.Result.Exceptions.Select(ex => ex.Message))}");
输出:
状态:RanToCompletion
结果:1
例外:fail1,fail2
Status: RanToCompletion
Results: 1
Exceptions: fail1, fail2
Caution: the implementation above ignores canceled tasks, and so does not mimic the default behavior of Task.WhenAll
:
如果提供的任务均未故障,但至少其中一项被取消,则返回的任务将以
Canceled
状态结束.
在这种情况下,更一致的行为是返回单个TaskCanceledException
.可以通过在WhenAllEx
内的最后return
命令之前添加以下代码来完成此操作:
A more consistent behavior would be to return a single TaskCanceledException
in this case. This can be done by adding this code inside the WhenAllEx
, just before the final return
command:
if (exceptions.Length == 0)
{
var canceledTask = tasks.FirstOrDefault(t => t.IsCanceled);
if (canceledTask != null)
{
exceptions = new[] { new TaskCanceledException(canceledTask) };
}
}
这篇关于一个任务失败时,是否可以从Task.WhenAll获得成功的结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!