忽略在 Task.WhenAll 抛出异常的任务,只获取完成的结果
Posted
技术标签:
【中文标题】忽略在 Task.WhenAll 抛出异常的任务,只获取完成的结果【英文标题】:Ignore the Tasks throwing Exceptions at Task.WhenAll and get only the completed results 【发布时间】:2017-01-28 02:46:12 【问题描述】:我正在处理一个任务并行问题,我有许多任务可能会或可能不会抛出异常。
我想处理所有正确完成的任务并记录其余的。 Task.WhenAll
传播任务异常,但不允许我收集其余结果。
static readonly Task<string> NormalTask1 = Task.FromResult("Task result 1");
static readonly Task<string> NormalTask2 = Task.FromResult("Task result 2");
static readonly Task<string> ExceptionTk = Task.FromException<string>(new Exception("Bad Task"));
var results = await Task.WhenAll(new [] NormalTask1,NormalTask2,ExceptionTk);
Task.WhenAll
抛出 ExcceptionTk
的异常,忽略其余结果。如何在忽略异常的情况下获得结果并同时记录异常?
我可以将任务包装到另一个任务中,try...catch()...
内部异常但我无权访问它们,我希望我不必添加此开销。
【问题讨论】:
您可以检查每个任务的状态。你会发现 NormalTask1 和 NormalTask2 是 RanToCompletion 而 ExceptionTk 是 Faulted。 捕获异常没有任何开销。如果异常频繁到您想忽略它们,您可以使用包装函数将所有结果转换为Result<TSuccess,TFailure>
类型并统一处理它们。这是面向铁路的编程风格的一部分
@PanagiotisKanavos 开销用于任务包装,而不是用于捕获异常。
糟糕,没有注意到您调用的是WaitAll
而不是WhenAll
。无论如何,使用WaitAll
,您已经进行了昂贵的阻塞操作。在任何情况下,将响应包装在结果类中都是更好的选择。我怀疑您有处理任务的管道,在这种情况下,您还应该查看 TPL 数据流
【参考方案1】:
你可以创建一个这样的方法来代替Task.WhenAll
:
public Task<ResultOrException<T>[]> WhenAllOrException<T>(IEnumerable<Task<T>> tasks)
return Task.WhenAll(
tasks.Select(
task => task.ContinueWith(
t => t.IsFaulted
? new ResultOrException<T>(t.Exception)
: new ResultOrException<T>(t.Result))));
public class ResultOrException<T>
public ResultOrException(T result)
IsSuccess = true;
Result = result;
public ResultOrException(Exception ex)
IsSuccess = false;
Exception = ex;
public bool IsSuccess get;
public T Result get;
public Exception Exception get;
然后你可以检查每个结果,看看是否成功。
编辑:上面的代码不处理取消;这是另一种实现:
public Task<ResultOrException<T>[]> WhenAllOrException<T>(IEnumerable<Task<T>> tasks)
return Task.WhenAll(tasks.Select(task => WrapResultOrException(task)));
private async Task<ResultOrException<T>> WrapResultOrException<T>(Task<T> task)
try
var result = await task;
return new ResultOrException<T>(result);
catch (Exception ex)
return new ResultOrException<T>(ex);
【讨论】:
这几乎就像面向铁路的编程。也许 OP 应该考虑更改原始函数本身以返回Result<TSuccess,TFailure>
值,或者使用包装函数将所有任务结果转换为 Result<>
如果一个任务被取消了怎么办?如果您也想处理这种情况,请将t.IsFaulted
替换为t.Exception != null
。
@svick 好点。检查t.Exception
不起作用,因为已取消的任务可能没有设置异常(只需检查Task.FromCanceled
)。请参阅我的更新答案。
一个可能的改进是使用 TaskContinuationOptions 为故障、取消和完成的延续定义单独的延续。这将使处理单独的情况变得更容易,并摆脱条件
我用你回答的第二部分来处理错误,谢谢。【参考方案2】:
您可以从其属性Result
中获取每个成功完成的Task<TResult>
的结果。
var NormalTask1 = Task.FromResult("Task result 1");
var NormalTask2 = Task.FromResult("Task result 2");
var ExceptionTk = Task.FromException<string>(new Exception("Bad Task"));
Task<string>[] tasks = new[] NormalTask1, NormalTask2, ExceptionTk ;
Task whenAll = Task.WhenAll(tasks);
try
await whenAll;
catch
if (whenAll.IsFaulted) // there is also the possibility of being canceled
foreach (var ex in whenAll.Exception.InnerExceptions)
Console.WriteLine(ex); // Log each exception
string[] results = tasks
.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToArray();
Console.WriteLine($"Results: String.Join(", ", results)");
输出:
System.Exception:错误的任务 结果:任务结果1,任务结果2
【讨论】:
谢谢你,Thodore,我没有测试你的代码,但我想当抛出异常时,WaitAll
退出,catch
处理日志记录。到那时,其他任务可能还没有返回,当我尝试收集结果时,一切似乎都在运行或取消。尝试在NormalTasks
(Thread.Sleep
) 上增加一些负载。
嗨@MenelaosVergis! Task.WaitAll
等待直到所有任务都已完成,成功或不成功(状态为故障或取消)。也许你把它和Task.WaitAny
混淆了?
你说得对,我把它和一些东西混淆了,不是Task.WaitAny
,而是Task.WhenAll
。
@MenelaosVergis 你可以看看这个问题:WaitAll vs WhenAll。通常Task.WhenAll
与Task.WaitAll
的使用方式不同。如果您只是在 try-catch 块中使用await Task.WhenAll
,您将只观察到一个异常。此行为是由await
运算符引起的。但是可以观察到所有的例外情况。只需将Task.WhenAll
返回的任务存储在一个变量中,然后在 catch 块中检查其Exception
属性。
是的,我自己也经历过,这就是我提出这个问题的原因。再次阅读问题,我已将拼写错误从WaitAll
更新为WhenAll
。【参考方案3】:
您可以添加带有异常处理的 HOC,然后检查是否成功。
class Program
static async Task Main(string[] args)
var itemsToProcess = new[] "one", "two" ;
var results = itemsToProcess.ToDictionary(x => x, async (item) =>
try
var result = await DoAsync();
return ((Exception)null, result);
catch (Exception ex)
return (ex, (object)null);
);
await Task.WhenAll(results.Values);
foreach(var item in results)
Console.WriteLine(item.Key + (await item.Value).Item1 != null ? " Failed" : "Succeed");
public static async Task<object> DoAsync()
await Task.Delay(10);
throw new InvalidOperationException();
【讨论】:
以上是关于忽略在 Task.WhenAll 抛出异常的任务,只获取完成的结果的主要内容,如果未能解决你的问题,请参考以下文章
为啥不等待 Task.WhenAll 抛出 AggregateException?
c# Task.WhenAll(tasks) 和 SemaphoreSlim - 如何知道所有任务何时已完全完成