Asynchronous programming in action
Jan 13 2024 04:17
If you have any I/O-bound needs (such as requesting data from a network, accessing a database, or reading and writing to a file system), you'll want to utilize asynchronous programming. You could also have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for writing async code.
Key pieces to understand
- Async code can be used for both I/O-bound and CPU-bound code, but differently for each scenario.
- Async code uses Task
and Task, which are constructs used to model work being done in the background. - The async keyword turns a method into an async method, which allows you to use the await keyword in its body.
- When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
- await can only be used inside an async method.
Here are two questions you should ask before you write any code
- Will your code be "waiting" for something, such as data from a database? If your answer is "yes", then your work is I/O-bound.
- Will your code be performing an expensive computation? If you answered "yes", then your work is CPU-bound.
Blocking vs Non-Blocking API
For example, I use HttpClient which is a class for sending HTTP requests and receiving HTTP responses from a resource identified by a URI.
HttpClient provides only non-blocking API, let's see this function Because GetAsync is non-blocking, so stopwatch.Stop() execute right away and results always < 20 milliseconds on my machine
public void GetProduct()
{
var client = new HttpClient();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
client.GetAsync("https://dummyjson.com/products/1");
client.GetAsync("https://dummyjson.com/products/2");
client.GetAsync("https://dummyjson.com/products/3");
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds); // Always < 20ms
}
If you want to calculate the total time of calling API in sequence, just simply add await before non-blocking API
await client.GetAsync("https://dummyjson.com/products/1");
await client.GetAsync("https://dummyjson.com/products/2");
await client.GetAsync("https://dummyjson.com/products/3");
But await is only available in async function, you need to add async keyword too, And an async method with a void return type does not follow the task asynchronous programming (TAP) model, we need replace void with Task
public async Task GetProduct()
The whole function became, execute time is huge (~1100 milliseconds) different, because Stopwatch needs to wait for all API calls to finish
public async Task GetProduct()
{
var client = new HttpClient();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
await client.GetAsync("https://dummyjson.com/products/1");
await client.GetAsync("https://dummyjson.com/products/2");
await client.GetAsync("https://dummyjson.com/products/3");
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds); // About ~1100 ms
}
}
To use the benefit of Asynchronous Programming/ non-blocking API, we can remove the await block on each API and use one await for all API calls by Task.WhenAll()
public async Task GetProduct()
{
var client = new HttpClient();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Task<HttpResponseMessage> getProductTask1 = client.GetAsync("https://dummyjson.com/products/1");
Task<HttpResponseMessage> getProductTask2 = client.GetAsync("https://dummyjson.com/products/2");
Task<HttpResponseMessage> getProductTask3 = client.GetAsync("https://dummyjson.com/products/3");
await Task.WhenAll(getProductTask1, getProductTask2, getProductTask3);
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds); // About ~780 ms
}
Execute time is about ~780ms, all data is fetched, and significantly time reduced compared to blocking call
Important info and advice
- async methods need to have an await keyword in their body or they will never yield!
- Add "Async" as the suffix of every async method name you write.
- Write code that awaits Tasks in a non-blocking manner
Use this... Instead of this... When wishing to do this... await
Task.Wait
orTask.Result
Retrieving the result of a background task await Task.WhenAny
Task.WaitAny
Waiting for any task to complete await Task.WhenAll
Task.WaitAll
Waiting for all tasks to complete await Task.Delay
Thread.Sleep
Waiting for a period of time
More important info and advice at Microsoft Learn
Delete comment
Confirm delete comment
Pham Duc Minh
Da Nang, Vietnam