使用asynchronous/等待多个任务

我正在使用完全asynchronous的API客户端,即每个操作都返回TaskTask<T> ,例如:

 static async Task DoSomething(int siteId, int postId, IBlogClient client) { await client.DeletePost(siteId, postId); // call API client Console.WriteLine("Deleted post {0}.", siteId); } 

使用C#5 async / await运算符,启动多个任务并等待它们全部完成的正确/最有效的方法是:

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait()); 

要么:

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray()); 

由于API客户端在内部使用HttpClient,我希望这会立即发出5个HTTP请求,并在每个完成时写入控制台。

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait()); 

虽然您与上面的代码并行运行操作,但是此代码会阻止每个运行的每个线程。 例如,如果networking调用需要2秒钟,则每个线程将挂起2秒而不进行任何操作,而是等待。

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray()); 

另一方面,上面的WaitAll代码也会阻塞这些线程,并且你的线程将不能自由处理任何其他的工作,直到操作结束。

推荐方法

我宁愿WhenAll将以并行方式asynchronous执行您的操作。

 public async Task DoWork() { int[] ids = new[] { 1, 2, 3, 4, 5 }; await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient))); } 

事实上,在上面的情况下,你甚至不需要await ,你可以直接从方法中返回,因为你没有任何延续:

 public Task DoWork() { int[] ids = new[] { 1, 2, 3, 4, 5 }; return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient))); } 

为了支持这一点,这里有一个详细的博客文章,经历所有的select和他们的优点/缺点: 如何和位置与ASP.NET Web API的并行asynchronousI / O

我很好奇地看到问题中提供的方法的结果以及接受的答案,所以我进行了testing。

代码如下:

 class Program { class Worker { public int Id { get; set; } public int SleepTimeout { get; set; } public async Task DoWork() { Console.WriteLine("Worker {0} started on thread {1} at {2}.", Id, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("hh:mm:ss.fff")); await Task.Run(() => Thread.Sleep(SleepTimeout)); Console.WriteLine("Worker {0} stopped at {1}.", Id, DateTime.Now.ToString("hh:mm:ss.fff")); } } static void Main(string[] args) { var workers = new List<Worker> { new Worker { Id = 1, SleepTimeout = 3000 }, new Worker { Id = 2, SleepTimeout = 3000 }, new Worker { Id = 3, SleepTimeout = 3000 }, new Worker { Id = 4, SleepTimeout = 3000 }, new Worker { Id = 5, SleepTimeout = 3000 }, }; Console.WriteLine("Starting test: Parallel.ForEach"); PerformTest_ParallelForEach(workers); Console.WriteLine("Test finished.\n"); Console.WriteLine("Starting test: Task.WaitAll"); PerformTest_TaskWaitAll(workers); Console.WriteLine("Test finished.\n"); Console.WriteLine("Starting test: Task.WhenAll"); var task = PerformTest_TaskWhenAll(workers); task.Wait(); Console.WriteLine("Test finished.\n"); Console.ReadKey(); } static void PerformTest_ParallelForEach(List<Worker> workers) { Parallel.ForEach(workers, worker => worker.DoWork().Wait()); } static void PerformTest_TaskWaitAll(List<Worker> workers) { Task.WaitAll(workers.Select(worker => worker.DoWork()).ToArray()); } static Task PerformTest_TaskWhenAll(List<Worker> workers) { return Task.WhenAll(workers.Select(worker => worker.DoWork())); } } 

结果输出:

测试输出

由于您调用的API是asynchronous的,因此Parallel.ForEach版本没有多大意义。 您不应该在WaitAll版本中使用Task.WhenAll ,因为这将失去并行性另一种方法是,如果调用程序是asynchronous,则在执行SelectToArray之后使用Task.WhenAll来生成任务数组。 第二个select是使用Rx 2.0