Task.WhenAll on List 的行为<Task>与 Task.WhenAll on IEnumerable <Task>的行为不同



在尝试捕获异常时,调用Task.WhenAll(IEnumerable<Task<T>>)和调用Task.WhenAll(List<Task<T>>)时,我看到了一些奇怪的行为差异

我的代码如下:

public async Task Run()
{
var en = GetResources(new []{"a","b","c","d"});
await foreach (var item in en)
{
var res = item.Select(x => x.Id).ToArray();
System.Console.WriteLine(string.Join("-> ", res));
}
}
private async IAsyncEnumerable<IEnumerable<ResponseObj>> GetResources(
IEnumerable<string> identifiers)
{
IEnumerable<IEnumerable<string>> groupedIds = identifiers.Batch(2);
// MoreLinq extension method -- batches IEnumerable<T>
// into IEnumerable<IEnumerable<T>>
foreach (var batch in groupedIds)
{
//GetHttpResource is simply a wrapper around HttpClient which
//makes an Http request to an API endpoint with the given parameter
var tasks = batch.Select(id => ac.GetHttpResourceAsync(id)).ToList();
// if I remove this ToList(), the behavior changes
var stats = tasks.Select(t => t.Status);
// at this point the status being WaitingForActivation is reasonable
// since I have not awaited yet
IEnumerable<ResponseObj> res = null;
var taskGroup = Task.WhenAll(tasks);
try
{
res = await taskGroup;
var awaitedStats = tasks.Select(t => t.Status);
//this is the part that changes
//if I have .ToList(), the statuses are RanToCompletion or Faulted
//if I don't have .ToList(), the statuses are always WaitingForActivation
}
catch (Exception ex)
{
var exceptions = taskGroup.Exception.InnerException;
DoSomethingWithExceptions(exceptions);
res = tasks.Where(g => !g.IsFaulted).Select(t => t.Result);
//throws an exception because all tasks are WaitingForActivation
}
yield return res;
}
}

最终,我有一个标识符的IEnumerable,我将其批处理为2个批次(在本例中为硬编码(,然后运行Task.WhenAll以同时运行每个2个批次。

我想要的是,如果两个GetResource任务中的一个失败,仍然返回另一个的成功结果,并处理异常(比如,将其写入日志(。

如果我在任务列表上运行Task.WhenAll,这正是我想要的。然而,如果我删除.ToList(),当我试图在await taskGroup之后的catch块中查找我的故障任务时,我会遇到问题,因为我的任务的状态仍然是WaitingForActivation,尽管我认为它们已经在等待。

当没有抛出异常时,ListIEnumerable的操作方式相同。只有当我试图捕捉异常时,这才开始引发问题。

这种行为背后的原因是什么?自从我进入catch块以来,Task.WhenAll一定已经完成了,但是为什么状态仍然是WaitingForActivation?我是不是没有抓住一些基本的东西?

除非使列表具体化(通过使用ToList()(,否则每次对列表进行枚举时,都会再次调用GetHttpResourceAsync,并创建一个新任务。这是由于延迟执行。

在处理任务列表时,我肯定会保留ToList()调用

最新更新