-1

在尝试捕获异常时调用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 个。

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

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

当没有抛出异常时,List和的IEnumerable行为方式相同。这只会在我尝试捕获异常时开始引起问题。

这种行为背后的原因是什么?Task.WhenAll自从我进入 catch 块以来,必须已经完成,但是为什么状态仍然存在WaitingForActivation?我在这里没有掌握一些基本的东西吗?

4

1 回答 1

2

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

在处理任务列表时,我肯定会保持ToList()通话

于 2020-04-07T16:38:42.720 回答