Async Await… Wait!!!! Patterns and Problems

6 minutes to read read

Async Await is one of the best feature that has changed a lot of dynamics in the recent past, but it has created same amount confusion and chaos. Lot of bad practices, wrong usage has plagued the very usage. I have refactored a very huge WPF code with lots of out ref and reference based codebase to new pattern, on covering ~80% of it, faced a few challenges reverted back. Then after a month took the task again and completed migration.

I do receive a lot of issues in PRs around this. Based on this feeling that there would be many others who are facing such isssue, this post is more targetted from experiences. There could be log of posts and videos and shit load of resources, but I would try to write it concisely and in simple language/. Again, comments are open for discussion.

Async Await Creates a new Thread

This is a myth and implicit assumption. Many people compare async await with an easier way to create new threads. Async/Await helps in building asynchronous threading model but itself doesn’t create any new threads. From microsoft

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model#BKMK_Threads

ConfigureAwait – the big must-have fish.

There are always a lot of arguments where to use ConfigureAwait and where not. Well I will provide a very crisp answer.

  • ConfigureAwait(false) if you are writing library code.
  • ConfigureAwait(true) if you are writing UI/frontend/user code.

Configure Await simply says, when the async task is done and re-entry is done, should it use the same thread or not. If you are writing a library or rather a nuget, it doesnt matter. However in case otherwise, UI Thread is required to post or update. await handles the dispatcher part swiftly.

Update Feb2020 – The default behavior in .net core is that it does not use the synchronization context. So in most cases either will have same behavior unless explicit implementation is provided.

.Wait, .Result, .WaitAll, WhenAll

Explaining them in one liner

  • .Wait – asks the thread to complete task. Will deadlock in case UI thread comes into picture.
  • .Result – asks the thread to complete task and provide result. Will deadlock in case UI thread comes into picture.
  • WaitAll – Same as Wait, but multiple tasks can be provided. Will deadlock in case UI thread comes into picture.
  • .WhenAll – A safer way, waits for completion of tasks provided asynchronously, return a single task which can be awaited.

ASYNC VOID – Heinous Crime

Heinous is a very strong word. And believe me this is definitely a very very bad to use it. Developers do tent to use it over time and land into a pit fall that serves numerous intermittent issues, corrupt data and sometimes leakage on the golden platter with silver spoon. Refer the following code snippet.

internal async void GetUrlContent()
{
    var content = await client.GetAsync("https://www.google.com/some-page/html");
    //Do something with content
}

Void here simply refers to a SYNchronous method or invocation point and we must not change its meaning. What is synchronous should remain synchronous. Here there are two options either change the method to async await chain by returning Task (discussed below), or if not possible, let it be synchronous only. This is very easy, there are two magic words (or rather methods).

GetAwaiter().GetResult()

Yes, these are the two methods which can help in getting any async method to be used in synchronous context. Since most of the new libs support async and many are async only, this could be master key to unlock it all. Internally it translates similarly to await. This can be used like

internal void GetUrlContent()
{
    var content = client.GetAsync("https://www.google.com/some-page/html").GetAwaiter().GetResult();
    //Do something with content
}

Downsides

There are two downsides. Current thread executing will wait for completion. This is any ways expected since the caller is synchronous in nature. We lose the async behavior, off-course that was expected.

Task – Return Everything as wrapped

Whatever response that an async method returns, it should be wrapped inside a Task. Yes this include void as well. So, what this means is that lets say, you are returning void, that should return Task. In case you are returning custom object, it should be passed on as Task<T>. Following snippet explains it better.

//before
internal void GetUrlContent()
{
    var content = client.GetAsync("https://www.google.com/some-page/html").GetAwaiter().GetResult();
    //Do something with content
}

//after
internal async Task GetUrlContent()
{
    var content = await client.GetAsync("https://www.google.com/some-page/html")
    //Do something with content
    await DoProcess();
}

Also, in case where a result is expected, following is taken.

//before
internal string GetUrlContent()
{
    var content = client.GetAsync("https://www.google.com/some-page/html").GetAwaiter().GetResult();
    //Do something with content
    return "ok";
}

//after
internal async Task<string> GetUrlContent()
{
    var content = await client.GetAsync("https://www.google.com/some-page/html");
    //Do something with content
    await DoProcess();
    return "ok";
}

Async Chain – Break it and lose it

Wherever at the first point of invocation where async method is called, the chain should not break. What this means is that once a async chain is started it should continue to async till the point of destination where it is ultimately taken care of. During complete chain of call, it should be async with Task return.

Async Modifier

Method NEED NOT have async modifiers. This totally depends on the point where async method is called opon. For instance, in the following method first part is better and more efficient. The reason is when we add an await over here, system internally creates a state machine and the delegation post the response over here which causes re entry.

When the last line of the method is actual operation, we can simply return it without awaiting.

//This is better approach. adding un necessary await can provide no additional benefit, but may lead to creation of unnecessary statemachines.
internal Task<string> GetUrlContentAsync()
{
    return DoProcess();
}
//THis is again a valid scenario, but should be avoided in general
internal async Task<string> GetUrlContentAsync()
{
    return await DoProcess();
}

Exceptions

This is another important aspect and has very simple one liner answer. Just do the awaiting well and your exceptions will work normally. Consider the following snippet from code which I used for internal training for awareness with in the team.

private static async Task RunTaskAsyncExceptionTest(bool waitForInput)
        {
            try
            {
                var instance = new AsyncTaskInstance();
                Console.WriteLine("1. Before RunAsyncVoid");
                //THIS LINE THROWS EXCEPTION FROM TASK <<------
                await instance.RunAsyncExceptionTask();
                Console.WriteLine("4. After RunAsyncVoid");
                if (waitForInput)
                {
                    Console.ReadLine();
                }

            }
            catch (NullReferenceException fx)
            {
                Console.WriteLine("in NullReferenceException exception");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"in Exception exception {ex.GetType()}");
            }
        }

Here the null reference exception is spilled from the async code behind. It is thrown out properly and also came handled in the first named catch block. So if you await properly, you dont need to handle anything explicitly.

Also one important aspect as implied from above as well is that catch will work only once awaited. The location of await and catch have to be together, but catch might just not work as expected on Tasks. Reason is tasks are returned instantly and waited only where they are awaited.

Additionally similarly you would see with references or using block. For instance, lets assume you have your DBContext which is handled using using block, and it is returned from repository, without awaiting, you might just see dbContext disposed error.

Lambda and Delegates

There are many cases where lambdas are used or rather Actions are used. In such a case if the action is having a await-able condition, there are chances you might end up putting async. That is just equivalent to async void and should be avoided. Consider the following example.

Foreach loop having async with await inside

In the above example, we have a foreach loop which provides a lambda which is an Action. Now an action is always void. What we did here is that we added an await internally and provided async keyword to definition. This acts as async void and is disastrous.

Closing Remarks

The above mentioned information should be good enough to handled basic async await cases. I do intend to write more or put this in more structured way, however, time is always a constraint. Please do put in your comments below or connect with over mail or linkedin.

2 comments

  1. Sir, this is explaine well without usual bulk load theory. I would like to know a bit more on the actual load handling, i mean final call where async programming ihappens and call flow. can we have a short call.

Leave a Reply

Your email address will not be published. Required fields are marked *