Imagine a scenario where you have a collection of tasks each one representing some asynchronous operation. You want to know when they have all completed, well that's easy enough we can just use Task.WhenAll. Lets also say you want to process the results of task as soon as it completes.
Ok so lets look at some code:-
Code:
private async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
public async Task Run()
{
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
}
So we can see the first method simply waits asynchronously for the specified amount of time and then returns the value passed in. So given the 3 tasks in the Run method we would expect to print out 1,2,3 if they completed in order.
How to do this. Well your fist attempt might be something like below:
Code:
public async Task Run()
{
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] {taskA, taskB, taskC};
await DefaultWay(tasks);
}
private async Task DefaultWay(IEnumerable<Task<int>> tasks)
{
foreach (var task in tasks)
{
Console.WriteLine("{0}", DateTime.Now.ToLongTimeString());
var result = await task;
Console.WriteLine(result);
}
}
However this will not work, instead they will execute asynchronously such that taskC will finish first followed by taskA and then TaskB but the results printed would be 2,3,1
Why is this? Well we know that when an await statement is reached and the task has not yet completed control will return to the caller of this await. In our case this is the Run method NOT the foreach loop.
Instead we need to create a higher level method that will await each task individually and then combine these results. There are many ways to accomplish this but the simplest way I have found is as follows:-
Code:
public async Task Run()
{
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] {taskA, taskB, taskC};
await DefaultWay(tasks);
var processingTasks = tasks.Select(async t =>
{
var result = await t;
Console.WriteLine(result);
});
await Task.WhenAll(processingTasks);
}
So now we await each task asynchronously also meaning control goes back to the processing tasks await. However each individual await will be returned to when complete so we get the desired 1,2,3
No comments:
Post a Comment