Follow us
| Careers Contact us

Introduction to asynchronous programming in .Net

Have you ever heard about asynchronous programming but didn’t know what it meant? Or perhaps you did some research and found out, but weren’t sure when to apply it? This blog post aims to demystify asynchronous programming, illustrating its advantages and when it’s most effective, especially for those collaborating with a technology consulting firm.

What is asynchronous programming?

By definition “asynchronous” means “Having many actions occurring at a time, in any order, without waiting for each other.”
The typical way of writing code is synchronously, meaning that each part of the code is executed as a result of some other part. This means that your program will wait for a certain process to finish executing (e.g. network call or I/O operation) – before continuing and thus, blocking the entire program for an unknown amount of time.

Writing code asynchronously will result in returning control back to the caller whenever a more time-consuming task occurs.

async programming

This means that, in simple words, whoever called your asynchronous method (e.g. another method) will be able to continue doing other work without waiting for the response. This will increase responsiveness of any User Interface the program might have and allow for better scalability.

Here is a graph that illustrates the difference between synchronous and asynchronous execution:

synchronous vs asynchronous programming


Or in other, simpler words:

Synchronous means -” Wait for me to finish before doing something else”.

Asynchronous means “It’s ok, keep going while I finish’”.

Why use asynchronous programming

Writing asynchronous code doesn’t necessarily improve performance in terms of the time it takes for your code to be executed. Rather, it improves the throughput capabilities of your system, making it able to handle more requests without failing.

By Utilizing the abilities of your server to handle requests, you make your system more responsive and easier to scale.

By definition “scalability” means “The capability of a system, network, or process to handle a growing amount of work, or its potential to be enlarged to accommodate that growth”.

There are a couple of methods to scale a server, so that it is able to handle more requests.

  • Horizontal scaling
  • Vertical scaling

With horizontal scaling, when the load becomes too great for a single server to handle, additional servers are added. Requests are distributed among these servers and thus, resolving the problem. Other problems might appear if the system is using a non-distributed database or cache component.

horizontal scaling in asynchronous programming

With vertical scaling when the load becomes too great for the server, its resources are increased, e.g. memory, storage, CPU power, allowing it to handle more requests.

vertical scaling in asynchronous programming


When to use it

As mentioned before, asynchronous implementation might not improve the code performance that much. This means that in order to gain the most benefits we must know when to apply it.

We have two types of time-consuming tasks that our code performs:

  • Computational bound tasks
  • I/O bound tasks

Computational bound tasks use the computation speed of the machine to run quickly. This means that the faster processor you have, the faster those tasks will be completed.

Computational bound tasks do not wait for or need any other input while occupying the time of the processor. Such tasks are expensive business algorithms or complex mathematical equations. They do not benefit from asynchronous programming and on some occasions using async code may slow down the whole process.

I/O bound tasks usually wait for the response of an external source before continuing with the execution. These external sources can be databases, APIs, services and others.

When making calls to external sources, the processor must wait for the response making I/O bound tasks the perfect candidate for an asynchronous approach. While the system is waiting, a “marker” is left, which the code returns to later. Meanwhile, control is given back to the caller of the method, possibly all the way up to the User Interface. This ensures that the user will not meet a blocked or non-responsive system while waiting for his request.


As the title implies, we will be using C# and some of the latest features of .Net in the examples below.

Async/ Await Keywords


The first thing you must do to implement a method asynchronously is mark it with the async modifier.

async modifier image
This will do two things:

  • It will allow for the await keyword to be used in this method once or several times
  • It will transform the method into a state machine when compiled

Just marking the method with the async modifier does not make it run asynchronously. Without using await inside the body of the method, it will run synchronously like any other method.

Async and Contracts (Interfaces)

Marking a method with the async modifier is an implementational detail. This means that you don’t specify whether the method is async in the contract (interface), which is helpful when, for example, you want to mock a certain method to return a particular value, otherwise received from an external source.

Async Return Types

Async methods can have the following return types:

  • From C# 5.x forward:

– Void

– Task

– Task

  • From C# 7.x forward:

– Any type with accessible GetAwaiter method

  • From C# 8.x forward:

– IAsyncEnumerable

Let’s briefly explain each of the above:

  • Avoid using void when writing asynchronous code, whenever you can. It’s only appropriate for event handlers in certain scenarios.
  • The Task class represents a single operation, that does not return a value and is usually executed asynchronously.
  • The Task class represents a single operation that is also executed asynchronously but returns a value.
  • Having an accessible GetAwaiter method allows any type to be returned with an async modifier.
  • IAsyncEnumerable is an async stream, enabling an async method to return collections. It is an asynchronous version of I Enumerable and can be used to iterate through asynchronous functions.


The await keyword is an operator. It can only be used inside the body of a method marked with the async modifier. What it does is:

  • It tells the compiler that the async method can’t continue until the awaited asynchronous process is completed
  • It tells the compiler to insert a possible suspension/resumption point into a method marked as async
  • It gives control back to the caller of the async method (potentially freeing up the thread)

async task
When you write “await someTask” the compiler generates code, that checks whether the operation has already been completed. If it hasn’t, the generated code hooks a continuation delegate (the marker) to the awaited object. When the operation is completed, the aforementioned continuation delegate will be called, thus re-entering the method and continuing the execution.


As of C# 7.x and onward an “Awaitable” is any type that exposes a GetAwaiter method, which returns a valid “Awaiter”. This means that such types can be used with the await keyword in an asynchronous method.


An “Awaiter” is any type returned from a GetAwaiter method that implements the System.Runtime.CompilerServices.INotifyCompletion interface and on occasions the System.Runtime.CompilerServices.ICriticalNotifyCompletion interface.


Best Practices

Avoid using “void” as a return type for your async methods

Always avoid using void as a return type when using an asynchronous method as it crashes the process in case of failure. Using “Task” will assure that a proper exception, which you can handle, is thrown.

async void

The right way to do the above is to use “Task.Run()” and “Task” to ensure your program won’t crash, if something goes wrong.

Task. Run

Avoid using Task.Result()

Applying “Taks.Result()” will result in blocking the current thread, while it waits for the result of your task, making the use of asynchronous programming meaningless.


Instead, you should “await” to get the required result.


Using “Task.Result()” is a way to implement Sync over Async but this is rarely the right way to go. If possible, you should strive to use a truly synchronous API, should synchronicity be required.

Use the .Net Core 3.x “IAsyncEnumerable” for collections

Task IEnumerable


The new, in .Net Core, IAsyncEnumerable allows for data to be sent back as soon as it’s processed.



Asynchronous programming continues to be a critical skill for developers in 2024, adapting to the latest technologies and frameworks. It allows you to do more while keeping the simplicity and readability of your code. Not only is it easy to implement and manage, but it also increases responsiveness, helps vertical scalability, and happens to be the best for long IO tasks with its simple syntax that gets regularly improved with C# versions. This evolving approach underscores its increasing importance in creating efficient, user-friendly applications, making it a key consideration for any software development firm or IT services consultancy aiming to stay at the forefront of technology.

What else do you need to give async a try? If you have any questions or suggestions on improving this content, feel free to leave them in the comment section below.

Also, if you enjoyed reading this post, please share it with your colleagues and friends. Happy coding!

Daniel Manolov is a Senior Software Consultant at Accedia. He has experience with various front-end and back-end technologies, as well as CMS systems, such as Sitefinity. He strives at always improving himself in all aspects of life.


Related Posts

Subscribe Now

Get exclusive access to company guides and resources and be the first to know about upcoming events! 

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Thank you!

You have been successfully subscribed!