Async/Await in C# Explained Simply

Asynchronous programming is simply a way to make a computer do two things at once. In .NET Core, it is almost required to write high performance servers that can communicate with multiple clients. An asynchronous model allows multiple things to happen at the same time and doesn’t block the flow of execution.

But before understanding asynchronous it is crucial to understand the concept of multi-threading.

Multi-threading

To understand multi-threading, let’s start with an analogy; let’s say we are communicating with a web server:

  • The server takes in request
  • It hits the controller
  • Code triggers a database action
  • Some data is returned to the user in the form of a web page

In our case, the CPU is sort like the air traffic controller that executes synchronously. While the web server sequences are taking place, we can’t really do anything else.

With the exception of being simple, this is bad. We want our code to be multi-threaded! But what is a thread? A thread is simply an execution context, but with the added specification that it only exists on one CPU core. Put even more plainly, thread is a basic unit of CPU utilization. Multi-threading allows multiple units of CPU utilization to happen on the same CPU core.

In our example, the database will be computationally expensive and operate outside the context of our CPU. With these characteristics, it is prime target for async/await.

What about multi-core CPU’s?

As of 2023, the maximum cores in a CPU is 192. That is the equivalent to 192 air traffic controllers compared to our previous example.

With a single core CPU, the work is divided into tiny parts and we are given the illusion that they running at the same time. Now with multi-core CPUs, things are actually running at the same time. This is commonly referred to as parallel processing.

Asynchronous programming

Whenever a CPU needs to do something “outside the system” (network call, file storage, I/O), it (ideally) should use asynchronous programming.

Asynchronous programming is freeing up the threads while operation happens by asking the system to send notification when the operation ends. The program will then sends a notification when the operation ends.

Illustrated in our example, instead of waiting for our database to finish executing; we allow the code to work on other things. Once the code gets done executing, it is usually returned in the form of an object (aka a “Task”).

Task in C#

The Task object is how we manage and interact with the async system. It allows us to do 3 things:

  • Let’s you know when a background operation finished
  • Add the ability to get the result of the background operation
  • Represent value produced by the background operation

The Task object is confusingly name, but a Task represents an event that may happen in the future. Task is pretty much the “Promise” of .NET.

Async/Await in C#

Modern asynchronous .NET applications use two keywords: async and await.

The async keyword is added to the method signature and doesn’t actually do anything. It is there to help developers identity async code.

The await keyword within the method generates a state machine for that method very similar to how yield return works. An async method returns Task if it returns a value.

With what we’ve just learn, let’s step through some code:

async Task PauseBeforeYelling()
{
    string turtle = "TURTLE";

    await Task.Delay(500);

    turtle += "!!!!!";

    await Task.Delay(200);

    Console.WriteLine(turtle);
}
  • Because the async keyword is just an identifier, async function technically start synchrnous
  • Once it reaches the await, it checks whether the operation is complete.
  • If complete, will execute synchronously.
  • If not complete, it will pause the aysnc method and return an incomplete task.

But what’s happening in await?

When the async operation is still going, the method waits asynchronously for the method to complete.

Essentially, the method is pausing and waiting for a continuation. One of the most difficult parts of async programming is learning how async functions return.

The easiest way to describe this is to compare the return to the yield keyword.

In software engineer, code that can be suspended and result is often called a coroutine. Essentially, async-await is suspending our code similar to a yield and returning instantly. This is different from how “normal” code executes where the stack is popped off and code returns.

Conclusing

The async rabbit hole goes deep, but I hope this blog was helpful in learning the fundamentals of async programming in C#. Happy coding!

Posted in C#

Leave a Reply

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