Before continuing asynchrony, I would like mention some misconception about being "asynchronous". Being asynchronous doesn't mean multi-threading or doesn't have to be multi-threaded. Asynchronous operation is simply used to avoid blockage of the working thread without the need of additional threads. Another falsely statement, "asynchrony results faster execution". Effectiveness of asynchrony depends solely on the tasks involved. Typically In-Memory operations doesn't required to be asynchronous. Tasks such as read operation of an remote file or operation on database or requesting over the network can be beneficial by using asynchrony. On the other hand, a task that requires significant amount of time to complete should be awaited, in other words, it can be beneficial if it is asynchronous. Also, asynchrony does not mean concurrency. Concurrency refers to the execution of multiple tasks, but they don't necessarily have to happen at the same time. While asynchrony allows tasks to proceed independently, without waiting for each other, concurrency focuses on the effective management of multiple tasks, whether they occur simultaneously or not.
One final misconception I would like to mention is "Asynchrony means parallel execution". This is false. Parallelism means executing multiple tasks at the same time. While asynchrony does allow parallelism, not all asynchronous operations are parallel. Asynchronous operations allow tasks to proceed independently without waiting, it doesn't necessarily be simultaneous execution.
Introduction
Synchronous operation, or simply the normal flow of code, pauses the thread when the task or any computation is not yet completed. Computation can be any sort of thing like performing CRUD operation, or anything that requires some resource. During the pause session, the thread is waiting for the computation to finish and it cannot perform other tasks. Executing tasks without blocking the thread for the responsiveness of the application or APIs is said to be asynchrony. The difference between synchronous and asynchronous is that the asynchronous code waits for the operation to complete without blocking the current execution thread. Normal synchronous flow of code blocks the thread when there is some operation going, and it does this until the operation has completed.
A bit of history to achieve Asynchrony in .NET
Early version of .NET introduced Asynchronous Programming Model (APM) to achieve asynchrony. APM used IAsyncResult
to achieve asynchronous operation, it has all the functionality to achieve asynchronous operation. IAsyncResult
provides properties to get information about the operation whether it has completed or not, the user state associated with the operation, and an asynchronous wait handle. To implement APM pattern, you would create two methods: Begin
and End
. Begin
was used to initiate asynchronous operation with parameters such as callback delegate, the user state, any other required information. Begin
returns IAsyncResult
object. End
was used as callback to retrieve the result of the operation. End
takes IAsyncResult
as a parameter which is returned by the Begin
method.
As an evolution to APM pattern, .NET introduced Event-based Asynchronous pattern (EAP). Event-based Asynchronous pattern (EAP) is to notify when an operation completes by utilizing events and delegates. In EAP, the initiation of an asynchronous operation is performed by invoking a method suffixed with 'Async' such as DoSomethingAsync
. Upon initiating the operation, an associated event, often named with a 'Completed' suffix, like DoSomethingCompleted
, is triggered upon completion of DoSomethingAsync
, signaling that the asynchronous task has finished. This event-driven approach allows to subscribe and handle the completion of events.
.NET 4.5 introduced Task-Based Asynchronous pattern (TAP) by utilizing Task Parallel Library (TPL). TAP has become the standard pattern to achieve asynchrony in C#. async
await
keyword and Task<TResult>
is used to achieve TAP. The Task<TResult>
class is particularly powerful in TAP, allowing to perform asynchronous operations that produce a result. This result can be awaited using the await
keyword, providing a clean way to handle asynchronous responses. async
keyword is used to denote that the method is asynchronous.
Showcasing asynchrony
Here is an simple example to demonstrate the importance of asynchrony. I will create an desktop application using Windows Forms and add two buttons: Non-Async Button
and Async Button
. Non-Async Button
's click event will have a synchronous flow whereas Async Button
will have an asynchronous flow. This example is based on TAP pattern. Here is the code:
// Non-async button event handler
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "Non-async button clicked. Pausing thread for 5 seconds...";
Thread.Sleep(5000); // replicating time consuming operation.
textBox1.Text = "Non-async operation completed.";
}
Before Thread.Sleep(5000);
, the textBox1
text field will also not be printed. It is due to the fact that the UI is not being updated during the Thread.Sleep(5000)
operation because this operation is blocking the UI thread.
// async button event handler
private async void button2_Click(object sender, EventArgs e)
{
textBox2.Text = "Async button clicked. delaying task for 5 seconds without pausing thread.";
await Task.Delay(5000); // replicating time consuming operation.
textBox2.Text = "Async operation completed.";
}
Output:
You can see that I can write text before pressing Non-Async Button
. But when I clicked Non-Async Button
, whole application freezes. I cannot write or highlight over the textfield. The Thread.Sleep(5000)
in the button1_Click
(Non-async button) method is being executed on the UI thread, causing the entire UI to freeze during that period (5 seconds). In GUI application like Windows forms, the UI thread is responsible for handling inputs, updating UI and responding to events. When a time-consuming operation is performed on the UI thread, such as using Thread.Sleep
, it freezes the entire UI because the thread is occupied with that operation and cannot perform its other duties.
But when I used async/await
pattern, there is no pause and thread blockage. The await Task.Delay(5000)
line represents an asynchronous delay of 5 seconds. During this period, the UI thread is not paused, and it can handle other tasks, respond to the user input, and update the UI as needed. Actually the code runs synchronously till await
. As soon as it reaches await
, it doesn't matter if the operation has completed or not, it will return. This is the power of the async/await
pattern. When the code reaches the await
keyword, it essentially says, "I'm going to pause here, but don't wait for me. Continue with other tasks you have to do, and when I'm ready, come back to this point." But how did the code ensure to update appropriate UI when something is awaiting? It does so by using SynchronizationContext
class.
A continuation is created when the code reaches await
. Continuation is basically a callback delegate that will be executed as soon as the awaited operation has completed. I will provide a detailed explanations on this topic in my upcoming blog post. Asynchronous operation is very crucial when responsiveness and non-blocking execution is your priorities.
Conclusion
In this post, we get a touch of asynchronous and some history of asynchrony in .NET. Next post, I will cover the async/await pattern, the significance of the await keyword, and insights into writing effective asynchronous code. Stay tuned.
Stephen Cleary's blog post on asynchronous operation has no thread.