如何将任务添加到Task.WhenAll等待的集合中?

5

Task.WhenAll 正在等待的列表中,是否有可能添加一个任务?

我想等待所有任务完成,在初始化后还有可能创建新的任务。但是在这里,Task.WhenAll 不会等待后面添加到集合中的新任务。

List<Task> tasks = new List<Task>();

var task1 = Task.Run(async () =>
    {
        Debug.WriteLine("task #1 started");
        await Task.Delay(TimeSpan.FromSeconds(20));
        Debug.WriteLine("task #1 finished");
    });

var task2 = Task.Run(async () =>
{
    Debug.WriteLine("task #2 started");
    await Task.Delay(TimeSpan.FromSeconds(30));
    Debug.WriteLine("task #2 finished");
});

var task3 = Task.Run(async () =>
{
    Debug.WriteLine("task #3 started");
    await Task.Delay(TimeSpan.FromSeconds(10));
    var inner = Task.Run(async () =>
    {
        Debug.WriteLine("inner task started");
        await Task.Delay(TimeSpan.FromSeconds(40));
        Debug.WriteLine("inner task finished");
    });

    tasks.Add(inner);

    Debug.WriteLine("task #3 finished");
});

tasks.Add(task1);
tasks.Add(task2);
tasks.Add(task3);

await Task.WhenAll(tasks);
Debug.WriteLine("All finished");

输出:

task #2 started
task #3 started
task #1 started
task #3 finished
inner task started
task #1 finished
task #2 finished
All finished
inner task finished < didn't wait for this to finish

如果您查看源代码,您会发现在内部机制启动之前,集合/数组已被复制。因此,对原始集合所做的任何更改都将不会被观察到。 - Damien_The_Unbeliever
我认为最好的方法是手动循环 - while(tasks.Count > 0) { await Task.WhenAll(tasks); tasks = tasks.Where(t => !t.IsCompleted); },这只是一个草图。 - Damien_The_Unbeliever
@Damien_The_Unbeliever 非常感谢。我认为这是最好的解决方案。你能否将其写成答案? - Blendester
@Blendester:我发现当人们问这个问题时,他们通常想要的是像TPL Dataflow这样的管道。 - Stephen Cleary
2个回答

7
这是因为tasks.Add(inner)await Task.WhenAll(tasks)之后执行。要回答你的问题,首先需要澄清任务#3内部任务之间的关系。在我看来,内部任务应该是任务#3的一部分。也就是说,任务#3在内部任务完成之前无法完成。
await inner;   //instead of tasks.Add(inner);   

谢谢。但那只是为了演示。新任务可以从任何地方添加到“tasks”中。我的问题是,当方法到达结尾时,我希望所有任务都已完成。也许我正在解决一个错误的问题。 - Blendester

4

Task.WhenAll 总是在实际开始等待之前复制任务的引用。这意味着,在等待正在进行时,原始集合可以被更新,但是 WhenAll 对这些更改完全不知情。

我建议的是,在循环中运行你的 WhenAll。类似于以下内容:

while(tasks.Count > 0) {
    await Task.WhenAll(tasks);
    tasks = tasks.Where(t => !t.IsCompleted);
}

(关于是否希望保持tasks本身不被修改,该变量的确切正确数据类型等具体细节,留给读者填写作为练习)

虽然我认为这是对原始问题的解决方案,但这绝对是臭代码。如果任务A依赖于另一个任务B的完成,那么任务A的实现应该在任务B上使用await。请参见下面Cheng Chen的响应以了解更多关于这种关系的信息。 - Cord Rehn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接