Introduction
If you are one of those who often wonder about what that much talked about Task Parallel Library in .NET does or if you ever wondered what should be the perfect scenario in your application to implement Async/Await, then this tip is for you. I have struggled with Async Await and TPL in my initial days, then I slowly gained the experience to see how it fits into my application.
In this tip, we are going to discuss about Task Parallel Library (a.k.a TPL) of .NET and slowly refactor our code to implement async/await. By the end of this tip, our aim will be to gain knowledge about where and how we should be using TPL/async-await/Continuation, etc. in our application. We are going to take a demo driven route.
P.S.: Start with the TPLDemo_UnresponsiveApp.zip. The completed solution is there in TPLDemo.zip.
Background
.NET Framework has evolved a lot from its classic Thread based approach to Task based approach. Making responsive application has become much easier. At first, let's take a look into what a unresponsive application is.
Please download the zipped file named - TPLDemo_UnresponsiveApp.zip. In that demo, we have written a simple XAML page with two TextBox
es for UserName
and Password
also there is one Login button.
Please run the application in Visual Studio (preferred version 2013). If you click on the Login Button and then click anywhere in the app, you will find that the app freezes up for 10 secs. The reason is we have spinned up a thread at the time of clicking the button, and that particular thread has made the UI unresponsive because it has blocked the UI thread. In this approach, we are trying to simulate what would happen to our application in case of a slow DB/Network call.
private void LogInButton_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(10000);
}
Now, our aim would be to make our application responsive and at the same time, it should feel like it runs much faster.
Introducing Task Parallel Library
To make this application responsive, we are going to implement Task Parallel Library. TPL was introduced in .NET 4.0 to execute concurrent as well as asynchronous code. Just remember one simple tip.
"Task represents an asynchronous operation."
So in our solution, we are going to take the following piece of code and run that as an asynchronous Task.
Thread.Sleep(10000);
To run it as a Task, we need to do two things:
- Import
System.Threading.Tasks
namespace;
using System.Threading.Tasks;
- Put our statement as a
Task
.
Task.Run(() => { Thread.Sleep(10000); });
If you are done till this point, then Congratulations - you have run your piece of code asynchronously. In this case, the method inside the braces have waited for 10 secs but it is not blocking UI thread so your application will behave in a proper responsive way.
You can now click on the Login button and check whether your app is responsive or not.
Going Deeper into Task Parallel Library
Now coming into a more practical requirement.
#Requirement 1: After the Task finishes, suppose you want to display a message or do any other custom action.
Solution: To schedule something to be executed once a Task is finished, we use "continuation". Task.Run( )
returns a Task Handler, we could make use of that and set up continuation.
Let's get our hands dirty with some code.
Replace the following code with:
Task.Run(() => { Thread.Sleep(10000); });
This:
var task = Task.Run(() => { Thread.Sleep(10000); });
Now, we will be able to apply continuation which will allow us to create a Task
which will run asynchronously once the Target task completes.
task.ContinueWith((t) => {
MessageBox.Show("Simulation completed");
});
#Requirement 2: Once the background simulation starts, you want to disable the Login Button and once it's completed, you want to enable the login button.
So we are going to disable the login button first and then enable it. We have the code snippet in one go. I think it is pretty much self explanatory.
LoginButton.IsEnabled = false;
var task = Task.Run(() =>{
Thread.Sleep(10000);});
task.ContinueWith((t) => {
MessageBox.Show("Simulation Completed");
Dispatcher.Invoke(()=>{
LoginButton.IsEnabled = true;
});
});
The only catch in the above code is the placement of IsEnabled
property. We have to keep in mind LoginButton
is a part of UI main thread so to access any property of that button, you have to be in UI thread. That is why in continuation we have to use Dispatcher.Invoke()
to return to UI thread. You can read more about it in this link.
Introducing Async/Await
Now, we will be refactoring our above code to use Async/Await.
First things first: We need to tell the compiler that our method is capable of running asynchronous code. So we will mark our method as async
.
private async void LogInButton_Click(object sender, RoutedEventArgs e)
So, in the previous demo, we started a simulation which runs for 10 secs under Task.Run()
. While it runs, we keep our LoginButton
disabled. Once the simulation completes, we display a message to the user also we do enable to LoginButton
as well.
Point to note: Marking a method Async
doesn't really directly run all your code asynchronously.
LoginButton.IsEnabled = false;
Task.Run(() =>
{
Thread.Sleep(10000);
});
Our aim is to run the continuation after this. So to tell the compiler to stop the execution in this part, we are going to introduce await
keyword.
Await
keyword will be applied to the part of our code which we want to Await
till the next executable statement comes into play.
LoginButton.IsEnabled = false;
await Task.Run(() =>
{
Thread.Sleep(10000);
});
MessageBox.Show("Simulation completed");
LoginButton.IsEnabled = true;
So, the second part of the code will run only after complete execution of the await
statement code.
Thus, Async/Await makes our life much easier whilst using Task.Run()
or Task Parallel Library.
Points of Interest
Using task parallel library along with Async/Await will be best way leverage Asynchronous programming in C#. Sometimes, we need to train your mind really sharp to know what is happening in the background of TPL or Async/Await. But once you do, it's really rewarding.
History
.NET has evolved a lot from Thread to Task to Async.