I should start with a proper warning- this article does not really illustrate what async and await keywords are. If you are not familiar with async and await keywords in C#, I recommend you go through the following MSDN articles:
- async (C# Reference)
- await (C# Reference)
- Asynchronous Programming with Async and Await (C# and Visual Basic)
This article is rather intended to illustrate the benefits we can get by using async and await properly.
First create a simple console application (I named it AsyncAwaitTest). Then, let us define a dummy class to access “time consuming” resources.
Consider the above class methods as some expensive operations which has async support (similar to .NET framework’s
DBContext.SaveChangesAsync). Important thing to keep in mind that, each of these methods takes 2 seconds to execute asynchronously.
Now, we write a simple class skeleton to test asynchronous method call performance.
As I can’t have
async Main method, I had to introduce a Run method which will run the sample tests we are about to write.
Now, we write a method DoMyTasksV1 which tries to access our asynchronous methods for accessing the resources.
We also add a call to DoMyTasksV1 method by changing our Run() method body as:
If you run the program now, you will see that it takes about 6 seconds to complete DoMyTasksV1. Well, that should be okay because each of these resource access methods takes 2 seconds to execute. So, it is simple math and you should be happy with the execution time.
Wait, aren’t we using asynchronous programming to achieve parallel execution of independent tasks? Well, yes. But what is the benefit of asynchronous programming if we can’t execute our tasks in parallel? I have seen a lot of codes using async/await the same way as we did in DoMyTasksV1 code. By using await, we are actually blocking the code for the asynchronous operation to complete. That is by no means asynchronous programming to me, though you are using async and await keywords.
So, how do we achieve truly asynchronous execution? Simple, if you know how tasks works. Basically each async method returns a Task to you when it starts executing it. It is you who should decide in your calling code when the task must be completed. In our DoMyTasksV1, we are requiring each async task to complete before the program execution can move on to the next line.
The question is, where to wait and where not. Well, in our DoMyTasksV1 method, you can see that we do not really have any dependency on SendEmailAsync() method. It may complete as late as just before returning from the method. However, we do need the two values from GetRandomNumberAsync() and GetSpecialStringAsync() methods before we can return. With this findings, we can now write more optimized version of our method.
If you change the method to call in Run() method and run the program now, you will see that the execution completes in just 4 seconds now. We just made it 33.33% efficient. What made it possible is- we are no longer waiting for the email sending procedure to complete. Rather we are running in parallel with other tasks. As we have to get the integer and string values before we can return, so these two methods must be awaited. That explains the 4 seconds delay- which seems logical. Or, is it? Lets see if we can improve it any further with our DoMyTasksV3.
If you run the program now with V3 code, amazingly it completes in 2 seconds- which is 66.67% improvement from V1 code. I think this is as far as optimizations go for this particular method.
I feel the need to clarify one thing explicitly, don’t get confused by when the DoMyTasksV1, V2, V3 method returns. Because by design of asynchronous programming, the method returns immediately. Rest of it executes when you await on the task. You can change the Run method as below to clear your confusions about when the task is returned.
Try it with V1, V2, V3 and see it always returns immediately.
I hope I was able to make my point. That is, when using asynchronous programming, we should always consider not to wait for tasks to complete- unless it is absolutely necessary. That’s all I wanted to say. Happy coding :)