Using async/await for multiple tasks
I'm using an API client that is completely asynchrounous, that is, each operation either returns Task
or Task<T>
, eg:
static async Task DoSomething(int siteId, int postId, IBlogClient client)
{
await client.DeletePost(siteId, postId); // call API client
Console.WriteLine("Deleted post {0}.", siteId);
}
Using the C# 5 async/await operators, what is the correct/most efficient way to start multiple tasks and wait for them all to complete:
int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());
or:
int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());
Since the API client is using HttpClient internally, I would expect this to issue 5 HTTP requests immediately, writing to the console as each one completes.
int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());
Although you run the operations in parallel with the above code, this code blocks each thread that each operation runs on. For example, if the network call takes 2 seconds, each thread hangs for 2 seconds w/o doing anything but waiting.
int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());
On the other hand, the above code with WaitAll
also blocks the threads and your threads won't be free to process any other work till the operation ends.
Recommended Approach
I would prefer WhenAll
which will perform your operations asynchronously in Parallel.
public async Task DoWork() {
int[] ids = new[] { 1, 2, 3, 4, 5 };
await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}
In fact, in the above case, you don't even need to await
, you can just directly return from the method as you don't have any continuations:
public Task DoWork()
{
int[] ids = new[] { 1, 2, 3, 4, 5 };
return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}
To back this up, here is a detailed blog post going through all the alternatives and their advantages/disadvantages: How and Where Concurrent Asynchronous I/O with ASP.NET Web API
I was curious to see the results of the methods provided in the question as well as the accepted answer, so I put it to the test.
Here's the code:
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()));
}
}
And the resulting output:
Since the API you're calling is async, the Parallel.ForEach
version doesn't make much sense. You shouldnt use .Wait
in the WaitAll
version since that would lose the parallelism Another alternative if the caller is async is using Task.WhenAll
after doing Select
and ToArray
to generate the array of tasks. A second alternative is using Rx 2.0
上一篇: 异步/等待作为协程的替代
下一篇: 使用异步/等待多个任务