Thursday, 19 June 2014

Async Await Part 2 - Processing Tasks In Order of Completion

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);


    //Each task is now await'd async itself and a new task is returned and then put into a new array where we can wait them all
    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

Async Await Part 1 - Return Types

I've decided to do a series of posts of Async Await which is the latest and greatest way to achieve asynchronous code in .Net. I'm doing this primarily as a brain dump of what helped me learn this very cool new(ish) feature.

So how does it work?
Well at a very simple level there are two new keyswords async and await. You mark you're method using the async keyword and return a Task or a Task<TResult>. You can also return void but this is NOT recommended except for top level event handlers.You then use the other keyword await inside your method body. Lets look at an example:-


private async Task AwaitAndProcessTasksAsync(Task<int> task)
{
    var result = await task;
    Console.WriteLine(result);
}
So above we have a very simple example of a method accepting a Task<int> and returning a Task. Now the first thing that struck me about this was where is the return statement? I mean we are returning a Task but nowhere in this method do a I see a return statement. Well it turns out that this is actually a void method, in fact it is exactly the same in functionality as the code below:-


Code:
private async void AwaitAndProcessTasksAsync(Task<int> task)
{
    var result = await task;
    Console.WriteLine(result);
}
There is a subtle but important difference though. Without the Task we cannot be sure when and if the method completed successfully. Hence we should always return Task. Should we want a return value then we simply need to return the value and it will be wrapped in a Task for us.