Back to: C#.NET Tutorials For Beginners and Professionals
Task-Based Asynchronous Programming in C#
In this article, I am going to discuss Task-Based Asynchronous Programming in C# with Examples. Please read our previous article where we discussed How to Control the Result of a Task in C# using TaskCompletionSource with Examples. In C#.NET, the task is basically used to implement Asynchronous Programming i.e. executing operations asynchronously and it was introduced with .NET Framework 4.0. Before understanding theory i.e. what is Task and what are the benefits of using Task, let us first discuss how to create and use Task in C#.
Working with Task in C#:
The Task-related classes belong to System.Threading.Tasks namespace. So the first and foremost step for you is to import the System.Threading.Tasks namespace in your program. Once you import the System.Threading.Tasks namespace, then you can create as well as access the task objects by using the Task class.
Note: In general, the Task class will always represent a single operation and that operation will be executed asynchronously on a thread pool thread rather than synchronously on the main thread of the application. If this is not clear at the moment then don’t worry we will discuss this in practice.
Example to Understand Task Class and Start Method in C#
We have already discussed async and await operators to create and execute the asynchronous methods. Now, let us try to understand how to implement asynchronous programming using the Task class. In the below example, we are creating the task object by using the Task class and then start executing the method asynchronously by calling the Start method on the Task object. The method which is pointed by the Task object is going to be executed when we call the Start method.
using System; using System.Threading; using System.Threading.Tasks; namespace TaskBasedAsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Statred"); Action actionDelegate = new Action(PrintCounter); Task task1 = new Task(actionDelegate); //You can directly pass the PrintCounter method as its signature is same as Action delegate //Task task1 = new Task(PrintCounter); task1.Start(); Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); Console.ReadKey(); } static void PrintCounter() { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); for (int count = 1; count <= 5; count++) { Console.WriteLine($"count value: {count}"); } Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); } } }
In the above example, we created the task object i.e. task1 using the Task class and then call the Start method to start the task execution. Here, task object task1 will create a new child thread to execute the defined functionality asynchronously on a thread pool thread. Here, the Task class constructor expects one Action delegate. You can create an instance of the Action delegate and pass that action delegate instance as a parameter to the constructor or you can directly pass a method whose signature is the same as the Action delegate. Now, when you run the above application, you will get the following output.
As you can see in the above output, two threads are used to execute the application code. The main thread and the child thread. And you can observe both threads are running asynchronously.
Example to Understand How to Create a Task object using Factory Property in C#
In the previous example, the method will start executing asynchronously when we invoke the Start method. In the following example, we are creating the task object using the Factory property which will start automatically means it will start executing the method immediately. Here, we don’t need to call the Start method.
Here, the Factory property of the Task class will return an instance of the TaskFactory object. And the TaskFactory class has one method called StartNew which will require an Action delegate as a parameter. So, we can create an instance of Action delegate and pass that instance as a parameter to this StartNew method or you can also directly pass a method whose signature must be matched with the Action delegate signature. For a better understanding, please have a look at the following example.
using System; using System.Threading; using System.Threading.Tasks; namespace TaskBasedAsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Statred"); Task task1 = Task.Factory.StartNew(PrintCounter); Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); Console.ReadKey(); } static void PrintCounter() { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); for (int count = 1; count <= 5; count++) { Console.WriteLine($"count value: {count}"); } Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); } } }
It will give you the same output as the previous example. The only difference between the previous example and this example is that here we are creating and running the task asynchronously using a single statement.
Example: Creating a Task object using the Run method
In the following example, we are creating a task by using the Run method of the Task class.
using System; using System.Threading; using System.Threading.Tasks; namespace TaskBasedAsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Statred"); Task task1 = Task.Run(() => { PrintCounter(); }); Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); Console.ReadKey(); } static void PrintCounter() { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); for (int count = 1; count <= 5; count++) { Console.WriteLine($"count value: {count}"); } Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); } } }
So, we have discussed three different ways to create and start a task in C#. From a performance point of view, Task.Run or Task.Factory.StartNew methods are preferable to create and start executing the tasks asynchronously. But, if you want the task creation and execution separately, then you need to create the task separately by using the Task class and then call the Start method to start the task execution when required.
Task using Wait in C#:
As we already discussed, the tasks will run asynchronously on the thread pool thread and the thread will start the task execution asynchronously along with the main thread of the application. So far the examples we discussed in this article, the child thread will continue its execution until it finishes its task even after the completion of the main thread execution of the application.
If you want to make the main thread execution wait until all child tasks are completed, then you need to use the Wait method of the Task class. The Wait method of the Task class will block the execution of other threads until the assigned task has completed its execution.
In the following example, we are calling the Wait() method on the task1 object to make the program execution wait until task1 completes its execution.
using System; using System.Threading; using System.Threading.Tasks; namespace TaskBasedAsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Statred"); Task task1 = Task.Run(() => { PrintCounter(); }); task1.Wait(); Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); Console.ReadKey(); } static void PrintCounter() { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); for (int count = 1; count <= 5; count++) { Console.WriteLine($"count value: {count}"); } Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); } } }
As you can see in the above code, we are calling the Wait() method on the task object i.e. task1. So, the main thread execution will wait until the task1 object completes its execution. Now run the application and see the output as shown in the below image.
So as of now, we have discussed how to work with tasks using different approaches. Now let us discuss what is Task and why should we use Task.
Task using Anonymous Method and Lambda Expression in C#:
In all our previous examples, we have executed a method using the Task. We have also seen that the Task class constructor, the Run method or the StartNew method expecting one Action delegate. So, instead of executing a method, we have also executed the logic using Anonymous Method as well as Lambda Expression. For a better understanding, please have a look at the following example.
using System; using System.Threading; using System.Threading.Tasks; namespace TaskBasedAsynchronousProgramming { class Program { static void Main(string[] args) { Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Statred"); #region Stat Method //Creating Task using Method Task task1 = new Task(PrintCounter); task1.Start(); //Creating Task using Anonymous Method Task task2 = new Task(delegate () { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); Task.Delay(200); Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); }); task2.Start(); //Creating Task using Lambda Expression Task task3 = new Task(() => { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); Task.Delay(200); Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); }); task3.Start(); #endregion #region StartNew //Creating Task using Method Task task4 = Task.Factory.StartNew(PrintCounter); //Creating Task using Anonymous Method Task task5 = Task.Factory.StartNew(delegate () { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); Task.Delay(200); Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); }); //Creating Task using Lambda Expression Task task6 = Task.Factory.StartNew(() => { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); Task.Delay(200); Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); }); #endregion #region Run //Creating Task using Method Task task7 = Task.Run(() => { PrintCounter(); }); //Creating Task using Anonymous Method Task task8 = Task.Run(delegate () { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); Task.Delay(200); Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); }); //Creating Task using Lambda Expression Task task9 = Task.Run(() => { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); Task.Delay(200); Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); }); #endregion Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); Console.ReadKey(); } static void PrintCounter() { Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Started"); Thread.Sleep(200); Console.WriteLine($"Child Thread : {Thread.CurrentThread.ManagedThreadId} Completed"); } } }
What Is a Task in C#?
A task in C# is used to implement Task-based Asynchronous Programming and was introduced with the .NET Framework 4. The Task object is typically executed asynchronously on a thread pool thread rather than synchronously on the main thread of the application.
A task scheduler is responsible for starting the Task and also responsible for managing it. By default, the Task scheduler uses threads from the thread pool to execute the Task.
What is a Thread Pool in C#?
A Thread Pool in C# is a collection of threads that can be used to perform a number of tasks in the background. Once a thread completes its task, then again it is sent to the thread pool, so that it can be reused. This reusability of threads avoids an application to create a number of threads which ultimately uses less memory consumption.
Why do we need to use a Task In C#?
Tasks in C# are basically used to make your application more responsive. If the thread that manages the user interface offloads the works to other threads from the thread pool, then it can keep processing user events which will ensure that the application can still be used.
That’s it for today. In the next article, I am going to discuss Chaining Tasks by Using Continuation Tasks in C# with Examples. Here, in this article, I try to explain Task-based Asynchronous Programming in C# using the Task class. I hope you understood how to create and use Task class objects in C#.
Nice Aricle
In the 1st example you provided, the main thread is getting executed 1st, completed then the child thread is executing. This is Synchronous in nature. So, in that case what is the use of the task? Can you please provide a little more detail?
No, It’s not like that, Task will be executed asynchronously in the background. The main thread is also executed parallelly and may or may not be completed before child threads, it is simply unpredictable. If you try to do some time taking task in the main thread, you will get to know that child threads are completed before Main thread.
does this gets checked?