List<Task> 上的 Task.WhenAll 与 IEnumerable<Task> 上的 Task.WhenAll 表现不同

Task.WhenAll on List<Task> behaving differently than 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 个批次(在此示例中硬编码),然后 运行ning Task.WhenAll 到 运行每批同时2个

我想要的是,如果 2 个 GetResource 任务中的 1 个失败,仍然 return 另一个任务的成功结果,并处理异常(例如,将其写入日志)。

如果我 运行 Task.WhenAll 在任务列表中,这完全符合我的要求。但是,如果我删除 .ToList(),当我试图在 await taskGroup 之后的 catch 块中找到我的错误任务时,我 运行 会遇到问题,因为我的任务状态仍然是 WaitingForActivation 虽然我相信他们已经被等待了。

当没有抛出异常时,ListIEnumerable的行为相同。这仅在我尝试捕获异常时才开始引起问题。

这种行为背后的原因是什么? Task.WhenAll 肯定已经完成,因为我进入了 catch 块,但是为什么状态仍然是 WaitingForActivation?我是不是没能掌握基本的东西?

除非您使列表具体化(通过使用 ToList()),否则每次您枚举列表都会再次调用 GetHttpResourceAsync,并创建一个新任务。这是由于 deferred execution.

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