Showcasing asynchronous programming in .NET Core 5

Sael Khlouf
3 min readApr 26, 2021

Asynchronicity Definition

In a synchronous programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk).

To achieve asynchronous programming with C#, we need to be aware of Tasks.

If you are familiar with JavaScript Promises, Tasks are quite the same.

A Task represents an asynchronous operation. It is an operation we want to perform and that is executed in the background. A Task is something like a promise, or a future, that will be executed at a later time. Tasks can be chained to be executed one after the other.

If the API was to work in an asynchronous manner, when a request is made to our API, a thread from the thread pool handles the request (as in the synchronous case). If the code makes an asynchronous I/O call (Task), the thread will be returned to the thread pool at the start of the I/O call and then be used for other requests (the thread will not be blocked).

Number of threads ?

For the .NET Core 5 Web API, I will set the number of worker threads to number of logical processors (2 * number of cores), which is 8 for my machine. Knowing that It is always a bad idea to do decide a large number of threads, that will cause performance degradation and increased complexity due to threads’ context switching.

For your information, number of max threads in whole system = Memory Size / (stack size)

System threads and logical processors in my machine

Stack size can be from 1MB to 8MB (generally)..

Using West Wind WebSurge 1.20 tool, I have made 4 requests/sec for a period of 1 minute, using the synchronous and asynchronous end-points, and the results were as follows:

Synchronously

results of concurrent requests when using synchronous programming

number of requests served = (238 requests /minute) or (4 requests / sec)

threads used and available using synchronous programming

And we have used all 8 threads to serve the incoming requests from time to time.

Asynchronously

results of concurrent requests when using asynchronous programming

number of requests served = (295 requests /minute) or (5 requests / sec)

threads used and available using asynchronous programming

Notice that number of requests served is more for asynchronous when compared to synchronous requests. This is because the threads did not get blocked.

And we have used only 4 threads to serve the incoming requests from time to time.

That shows better scalability and utilization of server resources. We didn’t need to block the threads while waiting for the timer to finish.

Here is the implementation of these two sync and async end-points:

Wrapping Up

To wrap up, asynchronous functions can be an incredibly useful tool in a developer’s toolbox. Asynchronous code allows for enhanced performance and a better user experience, resulting in an overall better application. The trick is knowing when to use asynchronous code — practice makes perfect!

Here is the Github repository: https://github.com/SaelKhlouf/FunWithAsync

--

--