Tuesday, November 14, 2017

C# async and await with Thread static

This is continuation of below post about async and await. That post discuss about how the async and await evolved and the basics. The same sample context is used in this post as well. So better read below post before continuing.

http://joymonscode.blogspot.com/2015/06/c-async-and-await-programming-model.html

Async Await and calling thread behavior

There is no objection that it simplified the reasoning about the code. But it may cause trouble, if we implement without understanding how it works. Let us see one example below.

public void Main()
{
           Console.WriteLine($"Main() - Thread Id - {Thread.CurrentThread.ManagedThreadId}");
           for (int counter = 1; counter < 5; counter++)
           {
               if (counter % 3 == 0)
               {
                   WriteFactorialAsyncUsingAwait(counter)
               }
               else
               {
                   Console.WriteLine(counter);
               }
           }
}
private async Task WriteFactorialAsyncUsingAwait(int facno)
{
    Console.WriteLine($"WriteFactorialAsyncUsingAwait() - Thread Id - {Thread.CurrentThread.ManagedThreadId} - Begin");
    int result = await Task.Run(()=> FindFactorialWithSimulatedDelay(facno));
    Console.WriteLine($"WriteFactorialAsyncUsingAwait() - Thread Id - {Thread.CurrentThread.ManagedThreadId} - Factorial of {facno} is {result}");
}


Guess what would be the thread ids printed from WriteFactorialAsyncUsingAwait(). Will those be same?

Those who says same, be prepared to spend nights and weekend debugging. Especially if you have something ThreadStatic before and after await. Below goes the output.

Main() - Thread Id - 1
1
2
WriteFactorialAsyncUsingAwait() - Thread Id - 1 - Begin
4
WriteFactorialAsyncUsingAwait() - Thread Id - 3 - Factorial of 3 is 6

In the code the await is executed in separate thread similar to its Task<> equivalent

Thread.ContinueWith and calling thread behavior

 Lets see what is its Task<> based implementation.

public void Main()
{
    Console.WriteLine($"Main() - Thread Id - {Thread.CurrentThread.ManagedThreadId}");
    for (int counter = 1; counter < 5; counter++)
    {
        if (counter % 3 == 0)
        {
            WriteFactorialAsyncUsingTask(counter);
        }
        else
        {
            Console.WriteLine(counter);
        }
    }
    Console.ReadLine();
}
private void WriteFactorialAsyncUsingTask(int no)
{
    Console.WriteLine($"WriteFactorialAsyncUsingTask() - Thread Id - {Thread.CurrentThread.ManagedThreadId} - Begin");
    Task<int> task=Task.Run<int>(() =>
    {
        int result = FindFactorialWithSimulatedDelay(no);
        return result;
    });
    task.ContinueWith(new Action<Task<int>>((input) =>
    {
        Console.WriteLine($"WriteFactorialAsyncUsingTask() - Thread Id - {Thread.CurrentThread.ManagedThreadId} - Factorial of {no} is {input.Result}");
    }));
}

See the output it is working the same way as of async await model.

Main() - Thread Id - 1
1
2
WriteFactorialAsyncUsingTask() - Thread Id - 1 - Begin
4
WriteFactorialAsyncUsingTask() - Thread Id - 4 - Factorial of 3 is 6

Why the consuming code is written inside ContinueWith({}) callback instead of reading the result from Task.Result property? If it is not via ContinueWith({}), the execution wait on task.Result line, hence the outer loop cannot move to next item. We loose all the benefits of Task then. Below goes the code for task.Result access and see how it blocks the outer loop from executing in parallel.



public void Main()
{
    Console.WriteLine($"Main() - Thread Id - {Thread.CurrentThread.ManagedThreadId}");
    for (int counter = 1; counter < 5; counter++)
    {
        if (counter % 3 == 0)
        {
            WriteFactorialAsyncUsingTask(counter);
        }
        else
        {
            Console.WriteLine(counter);
        }
    }
    Console.ReadLine();
}
private void WriteFactorialAsyncUsingTask(int no)
{
    Console.WriteLine($"WriteFactorialAsyncUsingTask() - Thread Id - {Thread.CurrentThread.ManagedThreadId} - Begin");
    Task<int> task=Task.Run<int>(() =>
    {
        int result = FindFactorialWithSimulatedDelay(no);
        return result;
    });
    Console.WriteLine($"WriteFactorialAsyncUsingTask() - Thread Id - {Thread.CurrentThread.ManagedThreadId} - End - Task Result - {task.Result}");
}

The output below shows clearly that the 4 is processed from the loop only after the 3 is processed. We lost the parallelism. The thread ids are same before and after.

Main() - Thread Id - 1
1
2
WriteFactorialAsyncUsingTask() - Thread Id - 1 - Begin
WriteFactorialAsyncUsingTask() - Thread Id - 1 - End - Task Result - 6
4

Moral of the story

Though async await seems easy to use, usage without understanding will take away our sleep and weekends.

No comments: