2

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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
yegor
  • 65
  • 5
  • Does this answer your question? [How and when to use ‘async’ and ‘await’](https://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await) – Lukasz Szczygielek Jun 28 '23 at 11:36
  • Try your two methods in a UI, but with a much higher loop count and compare the difference in responsiveness. Hint: The code is still asynchronous – Jamiec Jun 28 '23 at 11:36
  • 6
    "Asynchronous programming is a form of concurrent programming that allows two separate pieces of code to run simultaneously, as I understand it." No it doesn't do that at all. It allows a piece of code to be "parked" while it waits for something else to complete. It is possible to do this concurrently, using `Task.Run` but you don't *have* to do so, and normally would not. `Task.Run` is designed to offload CPU bound code to another thread. You can then `await` that whenever you want, in your case immediately. – Charlieface Jun 28 '23 at 11:39
  • Here is an article you could have a look https://medium.com/plain-and-simple/synchronous-vs-asynchronous-vs-concurrent-vs-parallel-4342bfb8b9f2 – Scryper Jun 28 '23 at 11:44

3 Answers3

5

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").

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • *"In your example, `Main` will return an incomplete `Task` when it reaches its `await`, and that thread is freed and not blocked by `Main`."* -- This sentence is a bit theoretical and idealized. [In practice](https://stackoverflow.com/questions/62723916/confusions-on-await-behavior-in-async-main/62725286#62725286) the main thread of a console application cannot be freed in any meaningful way, so it's blocked until the `async Main` completes. If it was not blocked, it would simply terminate, causing the whole process to terminate as well. – Theodor Zoulias Jun 28 '23 at 13:54
3

async/await is designed to help with I/O bound operations. Any time you read or write to a file, or an API or a database, then async/await helps reduce the number of threads and tasks, and allows the computer to process more simultaneously (because most of the time, the CPU is waiting for external things to catch up).

Your code is CPU bound, and is not a good fit for async/await. You CAN use async/await for CPU code, but it really just uses a single thread for the whole process.

Neil
  • 11,059
  • 3
  • 31
  • 56
  • 1
    _"Your code is CPU bound, and is not a good fit for async/await"_ - it depends. _"You CAN use async/await for CPU code, but it really just uses a single thread for the whole process"_ - the provided code can (and highly likely will in multithreaded environments) use multiple threads, though it will not be noticable with provided sideeffects. – Guru Stron Jun 28 '23 at 12:00
2

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

In short - no, you can have asynchronously executing code even in single-threaded systems which can't run separate pieces of code simultaneously.

To quote Stephen Toub:

There are two primary benefits I see to asynchrony: scalability and offloading (e.g. responsiveness, parallelism). Which of these benefits matters to you is typically dictated by the kind of application you’re writing. Most client apps care about asynchrony for offloading reasons, such as maintaining responsiveness of the UI thread, though there are certainly cases where scalability matters to a client as well. Most server apps care about asynchrony for scalability reasons, though there are cases where offloading matters, such as in achieving parallelism in back-end compute servers.

In this piece for most real life applications for scalability you can consider the IO-bound operations and for offloading for UI apps - both IO-bound and CPU-bound operations (Recognize CPU-bound and I/O-bound work). Since in your particular case there is no UI thread (or SynchronizationContext simulating something like it) and operations are CPU-bound there is no point in using async but if you will move this code into some UI event handler you will see the difference due to the await Method1 returning control of the UI thread until it is finished (Method2 and Method3 will execute on the UI thread and will block it, though it will not be noticable due to them being "fast").

So in short:

Does await make asynchronous code synchronous?

No it does not. But ability to observe the difference depends on the app.

See also:

Guru Stron
  • 102,774
  • 10
  • 95
  • 132