并行执行任务
好吧,基本上我有一堆任务(10),我想同时启动所有任务并等待它们完成。 完成后我想执行其他任务。 我读了很多关于这方面的资源,但我无法为我的具体情况做好准备……
这是我目前拥有的(代码已经简化):
public async Task RunTasks() { var tasks = new List { new Task(async () => await DoWork()), //and so on with the other 9 similar tasks } Parallel.ForEach(tasks, task => { task.Start(); }); Task.WhenAll(tasks).ContinueWith(done => { //Run the other tasks }); } //This function perform some I/O operations public async Task DoWork() { var results = await GetDataFromDatabaseAsync(); foreach (var result in results) { await ReadFromNetwork(result.Url); } }
所以我的问题是当我在等待任务完成时使用WhenAll
调用时,它会告诉我所有任务都已完成,即使它们都没有完成。 我尝试在我的foreach
添加Console.WriteLine
,当我进入延续任务时,数据不断从我以前没有完成的任务中进入。
我在这做错了什么?
您几乎不应该直接使用Task
构造函数。 在您的情况下,该任务仅触发您不能等待的实际任务。
您只需调用DoWork
并获取任务,将其存储在列表中并等待所有任务完成。 含义:
tasks.Add(DoWork()); // ... await Task.WhenAll(tasks);
但是,异步方法同步运行,直到达到未完成任务的第一个等待。 如果您担心该部分耗时太长,请使用Task.Run
将其卸载到另一个ThreadPool
线程,然后将该任务存储在列表中:
tasks.Add(Task.Run(() => DoWork())); // ... await Task.WhenAll(tasks);
基本上你是在混合两种不兼容的异步范式; 即Parallel.ForEach()
和async-await
。
为了你想要的,做一个或另一个。 例如,您可以使用Parallel.For[Each]()
并完全删除async-await。 Parallel.For[Each]()
只会在所有并行任务完成后返回,然后您可以转到其他任务。
代码也有一些其他问题:
-
你将方法标记为异步但不等待它(等待你在委托中,而不是方法);
-
你几乎肯定想要
.ConfigureAwait(false)
等待,特别是如果你不想在UI线程中立即使用结果。
如果你想使用TPL在不同的线程中运行这些任务的并行,你可能需要这样的东西:
public async Task RunTasks() { var tasks = new List> { DoWork, //... }; await Task.WhenAll(tasks.AsParallel().Select(async task => await task())); //Run the other tasks }
这些方法仅并行化少量代码:将方法排队到线程池并返回未完成的Task
。 此外,对于如此少量的任务,并行化可能比仅异步运行花费更多时间。 只有当您的任务在第一次等待之前执行更长时间(同步)的工作时,这才有意义。
对于大多数情况,更好的方法是:
public async Task RunTasks() { await Task.WhenAll(new [] { DoWork(), //... }); //Run the other tasks }
我的意见在您的代码中:
-
在传递给
Parallel.ForEach
之前,不应将代码包装在Task
。 -
您可以
await
Task.WhenAll
而不是使用ContinueWith
。
DoWork
方法是一种异步I / O方法。 这意味着您不需要多个线程来执行其中的几个,因为大多数情况下该方法将异步等待I / O完成。 一个线程足以做到这一点。
public async Task RunTasks() { var tasks = new List { DoWork(), //and so on with the other 9 similar tasks }; await Task.WhenAll(tasks); //Run the other tasks }
您几乎不应该使用Task
构造函数来创建新任务。 要创建异步I / O任务,只需调用async
方法即可。 要创建将在线程池线程上执行的任务,请使用Task.Run
。 您可以阅读本文以获取Task.Run
的详细说明以及创建任务的其他选项。
还要在Task.WhenAll周围添加一个try-catch块
注意:抛出System.AggregateException的一个实例,它充当一个或多个已发生的exception的包装器。 这对于协调多个任务(如Task.WaitAll()和Task.WaitAny())的方法很重要,因此AggregateException能够包含已发生的正在运行的任务中的所有exception。
try { Task.WaitAll(tasks.ToArray()); } catch(AggregateException ex) { foreach (Exception inner in ex.InnerExceptions) { Console.WriteLine(String.Format("Exception type {0} from {1}", inner.GetType(), inner.Source)); } }