运行多个异步任务并等待它们全部完成


265

我需要在控制台应用程序中运行多个异步任务,并等待它们全部完成再进行进一步处理。

那里有很多文章,但是我阅读的内容似乎越来越困惑。我已经阅读并理解了任务库的基本原理,但是显然我在某处缺少链接。

我知道可以链接任务,以便它们在另一个任务完成之后开始(这是我阅读的所有文章的场景),但是我希望所有任务同时运行,我想知道一次他们都完成了。

对于这种情况,最简单的实现是什么?

Answers:


440

这两个答案都没有提到等待的问题Task.WhenAll

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

Task.WaitAll和之间的主要区别在于,Task.WhenAll前者将阻塞(类似于Wait在单个任务上使用),而后者将不会且可以等待,从而将控制权交还给调用者,直到所有任务完成为止。

更重要的是,异常处理有所不同:

Task.WaitAll

至少一个Task实例被取消-或-在执行至少一个Task实例的过程中引发了异常。如果取消了任务,则AggregateException的InnerExceptions集合中将包含OperationCanceledException。

Task.WhenAll

如果提供的任何任务以故障状态完成,则返回的任务也将以“故障”状态完成,其中其异常将包含来自每个提供的任务的未包装异常集的集合。

如果提供的任务均未发生故障,但至少有一个被取消,则返回的任务将以“已取消”状态结束。

如果没有一个任务出错并且没有一个任务被取消,则生成的任务将以RanToCompletion状态结束。如果提供的数组/枚举不包含任何任务,则返回的任务将在返回给调用者之前立即转换为RanToCompletion状态。


4
当我尝试这样做时,我的任务会按顺序运行吗?是否需要先单独开始每个任务await Task.WhenAll(task1, task2);
Zapnologica

4
@Zapnologica Task.WhenAll不会为您启动任务。您必须为他们提供“热门”,这意味着已经开始。
Yuval Itzchakov

2
好。这就说得通了。那么您的示例将做什么?因为您还没有启动它们?
Zapnologica

2
@YuvalItzchakov非常感谢您!它是如此简单,但是今天对我有很大帮助!至少值+1000 :)
丹尼尔·杜谢克(DanielDušek)

1
@Pierre我没有关注。什么StartNew和纺纱新任务有他们所有异步等待吗?
Yuval Itzchakov

106

您可以创建许多任务,例如:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
我会推荐WhenAll
Ravi

是否可以使用await关键字而不是.Start()同时启动多个新线程?
Matt W'9

1
@MattW不,当您使用await时,它将等待它完成。在这种情况下,您将无法创建多线程环境。这就是在循环结束时等待所有任务的原因。
病毒

5
由于尚不清楚这是否会阻塞电话,因此请以后的读者投票。
JRoughan

出于某些原因,请参阅已接受的答案。
EL MOJO

26

我见过的最好的选择是以下扩展方法:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

这样称呼它:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

或使用异步lambda:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

您可以使用WhenAll它将返回一个等待的TaskWaitAll没有返回类型并类似地阻止进一步的代码执行,Thread.Sleep直到所有任务完成,取消或出错为止。

在此处输入图片说明

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

如果您想按特定顺序运行任务,则可以从此答案中获得启发。


对不起,您迟到了参加聚会的原因,但是为什么您await每次操作都必须同时使用WaitAllWhenAllTask[]初始化中的任务不应该没有await吗?
dee zg

@dee zg你是对的。上面的等待失败了。我将更改答案并将其删除。
NtFreX

对,就是那样。感谢您的澄清!(赞扬答案)
dee zg

8

您要链接Tasks还是可以并行方式调用它们?

对于链接
只需执行以下操作

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

并且不要忘记检查Task每个实例中的前一个实例,ContinueWith因为它可能有故障。

对于并行方式
,我遇到的最简单的方法是:Parallel.Invoke 否则,Task.WaitAll甚至可以使用WaitHandles进行倒数,以使剩余的动作减少为零(等待,这里有一个新类:)CountdownEvent,或者...


3
感谢您的回答,但您的建议可能会得到更多解释。
Daniel Minnaar 2014年

@drminnaar除了指向msdn的链接和示例以外,您还需要其他哪些解释?您甚至都没有点击链接,对吗?
Andreas Niedermair 2014年

4
我单击了链接,然后阅读了内容。我本来打算进行Invoke,但是关于它是否异步运行有很多If和But。您正在不断编辑答案。您发布的WaitAll链接是完美的,但是我寻求的答案以更快,更容易阅读的方式演示了相同的功能。不要冒犯,您的答案仍然是其他方法的不错选择。
Daniel Minnaar 2014年

@drminnaar这里没有冒犯,我只是很好奇:)
Andreas Niedermair 2014年

5

这就是我使用Func <>数组的方法:

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
您为什么不只将其保留为任务阵列?
Talha TalipAçıkgöz17年

1
如果您不小心@talha-talip-açıkgöz,则可以在不希望执行任务时执行它们。作为Func委托进行操作可以使您的意图明确。
DalSoft

5

还有一个答案...但是当需要同时加载数据并将其放入变量时,我通常会遇到这种情况,例如:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
如果LoadCatsAsync()LoadDogAsync()仅仅是数据库调用,则它们是IO绑定的。Task.Run()用于CPU限制工作;如果您正在等待数据库服务器的响应,则会增加不必要的开销。Yuval接受的答案是进行IO绑定工作的正确方法。
斯蒂芬·肯尼迪

@StephenKennedy您能否阐明哪种开销及其对性能的影响?谢谢!
Yehor Hromadskyi

在评论框中很难对此进行总结:)相反,我建议阅读Stephen Cleary的文章-他是这方面的专家。:从这里开始blog.stephencleary.com/2013/10/...
斯蒂芬·肯尼迪

-1

我准备了一段代码,向您展示如何在某些情况下使用该任务。

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
如何获得任务的结果?例如,对于在数据表中合并“行”(来自N个并行任务)并将其绑定到gridview asp.net?
PreguntonCojoneroCabrón
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.