使用Task.WhenAll增加任务列表

时间:2018-05-20 18:50:06

标签: c# .net task task-parallel-library

Task.WhenAll(IEnumerable<Task>)等待IEnumerable中的所有任务完成---但只有第一次调用时列表中的任务才会完成。如果任何活动任务添加到列表中,则不会考虑它们。这个简短的例子证明了:

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

    public async Task  QuickExample()
    {
        for(int n =0; n < 6; ++n)
            _tasks.Add(Func1(n));

        await Task.WhenAll(_tasks);     
        Console.WriteLine("Some Tasks complete");

        await Task.WhenAll(_tasks);
        Console.WriteLine("All Tasks complete");
    }


    async Task Func1(int n)
    {
        Console.WriteLine($"Func1-{n} started");
        await Task.Delay(2000);
        if ((n % 3) == 1)
            _tasks.Add(Func2(n));
        Console.WriteLine($"Func1-{n} complete");
    }

    async Task Func2(int n)
    {
        Console.WriteLine($"Func2-{n} started");
        await Task.Delay(2000);
        Console.WriteLine($"Func2-{n} complete");
    }

输出:

Func1-0 started
Func1-1 started
Func1-2 started
Func1-3 started
Func1-4 started
Func1-5 started
Func1-5 complete
Func1-3 complete
Func2-1 started
Func1-1 complete
Func1-0 complete
Func1-2 complete
Func2-4 started
Func1-4 complete
Some Tasks complete
Func2-4 complete
Func2-1 complete
All Tasks complete
Done

第二个Task.WhenAll()在这种情况下解决了问题,但这是一个相当脆弱的解决方案。在一般情况下处理这个问题的最佳方法是什么?

4 个答案:

答案 0 :(得分:2)

您正在修改List<>而不锁定它...您希望过着危险的生活:-)在执行Count之前保存_tasks的{​​{1}} ,然后在WaitAll检查WaitAll的{​​{1}}之后。如果不同,请进行另一轮(因此Count周围需要_tasks

while

我会添加第二个(可能更正确的解决方案),这与您正在做的不同:您只需WaitAll来自int count = _tasks.Count; while (true) { await Task.WhenAll(_tasks); lock (_tasks) { if (count == _tasks.Count) { Console.WriteLine("All Tasks complete"); break; } count = _tasks.Count; Console.WriteLine("Some Tasks complete"); } } async Task Func1(int n) { Console.WriteLine($"Func1-{n} started"); await Task.Delay(2000); if ((n % 3) == 1) { lock (_tasks) { _tasks.Add(Func2(n)); } } Console.WriteLine($"Func1-{n} complete"); } s的新await s生成它们,而不将它们级联到Task集合。如果A创建B,则A在B完成之前不会完成。显然,您无需将新Task添加到_tasks集合。

答案 1 :(得分:1)

异步函数将在第一个await返回给调用者 因此,在将额外任务添加到原始任务列表之前,for循环将完成。

Task.WhenAll的实现会将任务迭代/复制到本地列表,因此在调用Task.WhenAll之后添加的任务将被忽略。

在您的特定情况下,在Func1之前将呼叫转移到await Task.Delay()可能是一个解决方案。

async Task Func1(int n)
{
    Console.WriteLine($"Func1-{n} started");
    if ((n % 3) == 1)
        _tasks.Add(Func2(n));

    await Task.Delay(2000);
    Console.WriteLine($"Func1-{n} complete");
}

但是如果在实际场景中调用Func2取决于某种异步方法的结果,那么你需要一些其他解决方案。

答案 2 :(得分:0)

考虑一下;听起来工作正在提交给&#34;任务列表&#34;从另一个线程。从某种意义上说,&#34;任务提交&#34;线程本身也是另一个让你等待的任务。

如果您等待提交所有任务,那么保证您下次调用WhenAll将产生完整的有效负载。

您的等待功能可能/应该分为两个步骤:

  1. 等待&#34;任务提交&#34;任务完成,发信号通知所有任务已提交
  2. 等待所有提交的任务完成。
  3. 示例:

    public async Task WaitForAllSubmittedTasks()
    {
        // Work is being submitted in a background thread;
        // Wrap that thread in a Task, and wait for it to complete.
        var workScheduler = GetWorkScheduler();
        await workScheduler;
    
        // All tasks submitted!
    
        // Now we get the completed list of all submitted tasks.
        // It's important to note: the submitted tasks
        // have been chugging along all this time.
        // By the time we get here, there are probably a number of
        // completed tasks already.  It does not delay the speed
        // or execution of our work items if we grab the List
        // after some of the work has been completed.
        //
        // It's entirely possible that - by the time we call
        // this function and wait on it - almost all the 
        // tasks have already been completed!
        var submittedWork = GetAllSubmittedTasks();
        await Task.WhenAll(submittedWork);
    
        // Work complete!
    }
    

答案 3 :(得分:0)

由于在执行原始任务列表的过程中似乎可以创建其他任务,因此您需要一个简单的while构造。

while (_tasks.Any( t => !t.IsCompleted ) 
{
    await Task.WhenAll(_tasks);
}

这将检查列表中是否有任何未完成的任务并等待它们,直到它在没有任务的情况下捕获列表。