Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does await make asynchronous code synchronous?

I was learning how do async and await work in programming. I came across this example on the internet saying that this is asynchronous code:

class Program
{
    static async Task Main(string[] args)
    {
        Method2();
        var count = await Method1();
        Method3(count);
        Console.ReadKey();
    }

    static async Task<int> Method1()
    {
        int count = 0;
        await Task.Run(() =>
        {
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine(" Method 1");
                count += 1;
            }
        });
        return count;
    }

    static void Method2()
    {
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(" Method 2");
        }
    }

    static void Method3(int count)
    {
        Console.WriteLine("Total count is " + count);
    }
}

Asynchronous programming is a form of concurrent programming that allows two separate pieces of code to run simultaneously, as I understand it. But all these await operators make the code run consistently and actually, we could use the following example, which gives the same output:

class Program
{
    static async Task Main(string[] args)
    {
        Method2();
        Method3(Method1());
        Console.ReadKey();
    }

    static int Method1()
    {
        int count = 0;
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(" Method 1");
            count += 1;
        }
        return count;
    }

    static void Method2()
    {
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine(" Method 2");
        }
    }

    static void Method3(int count)
    {
        Console.WriteLine("Total count is " + count);
    }
}

So why do I use await operators when they essentially make asynchronous code synchronous?

like image 341
Egor Bobrov Avatar asked Sep 26 '26 04:09

Egor Bobrov


1 Answers

I came across this example on the internet saying that this is asynchronous code

Sort of. It's what I call "fake asynchrony": the code uses await Task.Run to asynchronously wait for synchronous work done by a thread pool thread. This is in contrast with "true asynchrony", which is generally I/O (i.e., there's no thread pool thread waiting for the operation to complete). "Fake" asynchrony doesn't mean it's bad, though; it's a great fit for UI applications.

Asynchronous programming is a form of concurrent programming that allows two separate pieces of code to run simultaneously, as I understand it.

I would say that concurrent programming is running separate pieces of code simultaneously. Then there are two different kinds of concurrent programming: parallelism and asynchrony. Parallelism uses multiple threads to run concurrently; asynchrony uses a nonblocking approach to run concurrently.

The key takeaway is that asynchrony doesn't block the calling thread. Its purpose is to free up threads.

So, back to the "fake asynchrony": from one perspective, yes, it's asynchronous because it doesn't block the calling thread. Yet I call it "fake asynchrony" because it blocks a thread pool thread in order to free up the calling thread. Again, this is perfectly fine in some scenarios, e.g., if the calling thread is a UI thread and you want to keep the UI responsive.

But all these await operators make the code run consistently ... So why do I use await operators when they essentially make asynchronous code synchronous?

I believe the term you're looking for is "serial", which is often confused with "synchronous". Serial code runs one at a time. In the example you posted, a very common pattern is used:

var count = await Method1();

So, Method1 is started and returns (this is asynchronous code, so Method1 returns a Task before it completes; the already-returned Task will be completed when Method1 completes). Then the calling thread does an immediate await.

This is a very common pattern used to ensure the progress is serialized: we don't want Main to continue executing until Method1 completes and we can get the result into count.

However, "serial" does not mean "synchronous". All synchronous code is serial by nature (you have to use parallelism to impose concurrency). And most asynchronous code is also serial. However, it is asynchronously serial; the calling thread is not blocked. In your example, Main will return an incomplete Task when it reaches its await, and that thread is freed and not blocked by Main.

The benefit can be hard to see in a Console application. But if the thread is a UI thread, then your UI can remain responsive. If the thread is an ASP.NET thread pool thread, then it can handle other requests, increasing the scalability of your server (note that this is not true for "fake asynchrony" on ASP.NET - only "true asynchrony").

like image 168
Stephen Cleary Avatar answered Sep 27 '26 16:09

Stephen Cleary