Alvan's digital garden

Learning Asynchronous Programming - Notes

๐Ÿš€ What are the incentives?

It is my third week working as a Software Developer at a company which I have been given only one task so far. Task’s details are protected under NDA, so, I can’t tell what exactly it was. But I’m having problems with it so, it turns out, I have to learn more about this “Async programming”.


๐ŸŽฏ What are the goals for today?

Well, I hope for better understanding of Task :D


๐Ÿ Let’s go!

โš ๏ธ Disclaimer

This post is not a comprehensive guide to this topic. These are just notes I take while researching and learning this topic.

Task VS Thread

It turns out, differentiating these concepts is kinda the key point. Task has nothing to do with Thread. Basically,

Yes, it gets interesting. I was told Task is lightweight Thread. Looks like I have been tricked a bit :D

We have been tricked

Task:

Task represents a work that needs to be done. It can be completed right now or after some time.

Task can be completed, or faulted. It is it’s all duty - keeping track whether a work is completed or not. Only if some Task is not faulted during execution, meaning it completed, next Task is scheduled.

The thing that controls this task scheduling is called, very intuitively, TaskScheduler.

Thread:

Threads are very different concept. It represents execution of code. It keeps track of what is executing and where is it executing? Threads has SynchronizationContext associated which is used to communicate between different types of threads.

Threads execute tasks scheduled by a TaskScheduler


What exactly is Await?

Let’s assume we have a program that takes long time to finish. Most of the times this will be some I/O bound code. This kind of operations does not require that much CPU while waiting for third party “thing”. For example, fetching string from a server with HttpClient.

public async Task<string> ReadStringFromServer(string url)
{
    var request = await _httpClient.GetAsync(url);
    var finalString = await request.Content.ReadAsStringAsync();
    return finalString;
}

The above code demonstrates proper way of fetching data. But why is so?
As we know, using network is costly operation, as we have to wait for other server’s response. But in this case, we are not exactly waiting for that server’s response. Instead, we are using that precious time for handling other requests or completing other Task.

But how this is possible?

Of course, because of (not only) that async/await keywords. Actually, as we are diving deeper than usual, it is not those keywords doing magic. It is ContinueWith/Unwrap APIs from Task class. Basically, async&await are just syntactic sugar for following code:

public async Task<string> ReadStringFromServer(string url)
{
    var requestTask = HttpClient.GetAsync(url);
    var readStringTask = request.ContinueWith(http => 
        http.Result.Content.ReadAsStringAsync());
    return download.Unwrap();
}

Here, what TaskScheduler does is:

So, every Task we use is added to a queue and is executed one-by-one. As we stated earlier, this is all done by TaskScheduler.

*By far, all we did was for sake of optimizing time for execution.

About DeadLock

DeadLock is when our executing thread is suspended and waiting for some work to be complete, meaning this particular Thread is allocated just for some particular Task. This is exactly what we are trying to avoid, as Threads are expensive and very limited.

Let’s consider following code. Here, ReadStringFromServer is sync method, meaning caller thread will be blocked and waiting for some work to complete.

public string ReadStringFromServer(string url)
{
    // Never NEVER never ever! use task.Result
    var request = _httpClient.GetAsync(url).Result;
    var finalString = request.Content.ReadAsStringAsync().Result;
    return finalString;
}

What is happening here is:

But in actuality this code depends on context (where method is called). And problem really is this:

Here, third case gives us very good chance to make call to async function from sync one. For example, while working from UI thread, if we allocate separate thread to just this function, it won’t cause huge problems to us.

public string ReadStringFromServer(string url)
{
    Task.Run(async () => 
    {
        var request = await _httpClient.GetAsync(url);
        var finalString = await request.Content.ReadAsStringAsync();
        return finalString;
    }).Result;
}

What below code (specifically Task.Run) does is forcing task to execute in Thread Pool thread. So, this code is okay when called from a thread other than Thread Pool.

Well, we still have opportunity to make this code worst. Let’s try smth like this:

public string ReadStringFromServer(string url)
{
    Task.Run(() => 
    {
        var request = _httpClient.GetAsync(url).Result;
        var finalString = request.Content.ReadAsStringAsync().Result;
        return finalString;
    }).Result;
}

This is where the disaster happens.

The code below will deadlock application, no matter what context we are calling it from.

๐Ÿ“Œ I will continue writing on this topic as I learn

๐Ÿ“š References:

#daily #learning