Async Await and Parallelism
In C#, async and await was first introduced in C# 5 detail explanation in MS Docs. You should go ahead and read those full articles. Starting from MVC 4, the async controller was introduced with async/await. Since then developers have been using async/await whenever possible.
What if a senior developer being asked: Hey explain async/await for juniors. We think we know something is just step 1. And that understanding might be wrong until we can explain to someone else.
Some believe that having asynchronous operations will make the system faster because more threads are used. Some say async will not block the main thread allowing better responsiveness of Windows Applications, or serving more requests in a Web Application.
How could we explain them? Not so many people have deep knowledge to dig deep in the low level implementation. Given that someone can (yes there are many of them), it is not easy to explain to others. Where is the balance? How about mapping the complex concept with something familiar?
I am not discussing the performance part, or will it be faster than sync. That is a complex topic and depending on many factors. Let’s analyze the serving more requests in a Web Application part.
Here is the truth, if you are in doubt ask Google
Async controller actions will allow a web application serves more requests.
Welcome to the Visa Department! You are here to get your Visa to US. There are 3 people in the department: Bob, John, and Lisa. Everyday there are hundreds of citizens come.
An applicant comes, Bob takes it. He does all the steps defined in the process. He might finish and come back 3 hours later. An applicant is solved and returned to the client.
When Bob is busy, John and Lisa take the next applicants and the process repeats. If Bob, John, and Lisa have not finished their applicants, there are just 3 applicants served. The rest has to wait.
There are 3 applicants served. The rest has to wait.
That is ASP.NET MVC synchronous controller in action. It blocks the processing pipeline.
The three decide to make a change. They organize their work and responsibilities
- Bob: Take incoming applicants. Put them on the boxes (labeled Requests Box) on tables next to him.
- John, Lisa: Take applicants in the boxes, proceed them. When an applicant is proceeded, put it in other boxes (labeled Responses Box) in the department.
- Whenever there is an applicant in the Responses Box, whoever (Bob, John, or Lisa) is free, take the applicant and return to the client.
What have changed in this model?
- Bob can take as many applicants as he can. Many clients are served.They can return to their seat, take a coffee and wait for the result.
- John and Lisa can coordinate or utilize the resources they have to finish the job.
- Anyone can return the result. Bob receives the applicant but maybe Lisa returns the result.
Is it faster to proceed one applicant? We do not know.
Can we serve many clients (applicants) in a day? Yes, definitely!
That is the ASP.NET MVC async controller in action.
Concept mapping
- Citizen (visa application): Request
- Visa Department: Web Server hosts the application.
- Bob, John, and Lisa: Thread
- Proceed an applicant: Application domain logic.
- Accept an applicant: Controller Action.
Ok Cool! Let’s see some code.
public class ThreadModel { public int Id { get; set; } public string Message { get; set; } } public class ThreadTestController : Controller { [HttpGet] public async Task<ActionResult> Info() { var stopwatch = new Stopwatch(); stopwatch.Start(); var model = new List<ThreadModel>(); model.Add(new ThreadModel { Id = Thread.CurrentThread.ManagedThreadId, Message = "Bob receives a visa applicant" }); await Task.Delay(TimeSpan.FromSeconds(30)); stopwatch.Stop(); model.Add(new ThreadModel { Id = Thread.CurrentThread.ManagedThreadId, Message = $"Lisa returns the applicant after: {stopwatch.Elapsed}" }); return Json(model); } }
And the outcome
[{"id":3,"message":"Bob receives a visa applicant"}, {"id":25,"message":"Lisa returns the applicant after: 00:00:30.0024240"}]
The request is handled at the thread 3 (Bob). And the response is handled at the thread 25 (Lisa). The elapsed time is 30 seconds.
Ok, then let’s see how long would it take if we await twice
public class ThreadModel { public int Id { get; set; } public string Message { get; set; } } public class ThreadTestController : Controller { [HttpGet] public async Task<ActionResult> Info() { var stopwatch = new Stopwatch(); stopwatch.Start(); var model = new List<ThreadModel>(); model.Add(new ThreadModel { Id = Thread.CurrentThread.ManagedThreadId, Message = "Bob receives a visa applicant" }); await Task.Delay(TimeSpan.FromSeconds(30)); await Task.Delay(TimeSpan.FromSeconds(30)); stopwatch.Stop(); model.Add(new ThreadModel { Id = Thread.CurrentThread.ManagedThreadId, Message = $"Lisa returns the applicant after: {stopwatch.Elapsed}" }); return Json(model); } }
And the result
[{"id":29,"message":"Bob receives a visa applicant"}, {"id":32,"message":"Lisa returns the applicant after: 00:01:00.0099118"}]
It is 1 minute. Can we make it faster? How about this?
public class ThreadTestController : Controller { [HttpGet] public async Task<ActionResult> Info() { var stopwatch = new Stopwatch(); stopwatch.Start(); var model = new List<ThreadModel>(); model.Add(new ThreadModel { Id = Thread.CurrentThread.ManagedThreadId, Message = "Bob receives a visa applicant" }); var t1 = Task.Delay(TimeSpan.FromSeconds(30)); var t2 = Task.Delay(TimeSpan.FromSeconds(30)); await Task.WhenAll(t1, t2); stopwatch.Stop(); model.Add(new ThreadModel { Id = Thread.CurrentThread.ManagedThreadId, Message = $"Lisa returns the applicant after: {stopwatch.Elapsed}" }); return Json(model); } }
Hey, look
[{"id":4,"message":"Bob receives a visa applicant"}, {"id":27,"message":"Lisa returns the applicant after: 00:00:30.0134799"}]
It is 30 seconds.
Asynchronous programming is a hard job. A proper understanding is very important. You do not understand unless you can explain.