Task is a concept used in parallel programming.Parallel programming and Threading are related as both are used for asynchronously executing the code.This means that different parts of our application can execute in parallel.For example User Interface can be running at the same time our application is busy performing a long running computation.
Threading is a concept that has been used since long for creating asynchronous applications.When executing multiple threads each thread gets a time slice.This means that each thread gets small portion of processor time after which the next thread is executed by the same processor.
But today the machines are having much more processing power and consists of multiple processor cores.Tasks executes in parallel on these cores unlike threads which are executed by the same processor.So one task can be executed by one core and second task can be executed by the other one.
So tasks make better utilization of the processors of today than the threads.Tasks are a part of TPL which was introduced in .NET Framework 4.Task is represented by the Task class in the System.Threading.Tasks namespace.So we need to import this namespace to work with Tasks in our application.
using System.Threading.Tasks;
After we import the above Tasks namespace we can work with Tasks in our application.
Running Tasks
There are different ways by which we can create and run Tasks.Following are some of the ways:
Call the Start() method of the Task
We can directly create an instance of Task class and pass an Action delegate as the constructor argument.
Task task = new Task(new Action(Hello)); public static void Hello() { Console.WriteLine("Hello Task"); } task.Start();
or we can use lambda expression as the constructor parameter
Task task = new Task(()=> { Console.WriteLine("Hello Task"); }); task.Start();
Call the Task.Run() static method
We can call the static Run method of the Task class.We pass Action delegate to this Run() method.
Task task= Task.Run(() => { Console.WriteLine("Hello Task"); });
When we call the Run method it returns a Task object.We can use this Task object to get information about the running task such as the Status of the Task.To get the status of the task we use the Status property.
Call Task.Factory.New() method
Run method is not available in framework 4.0 so we need to use the Task.Factory.New in framework 4.0
Task.Factory.StartNew (() => Console.WriteLine ("Hello Task!"));
Returning values from Task
If we create the non generic Task class instance then we can not retreive any return value from the task.If we want to retrieve the return value from the task then we need to use the generic Task<T> ,T is the type of data that the task will return.
Once we assign a Task instance to the Task<T> variable,we can retrieve the return value by accessing the Result property of the Task.
Task<string> task = new Task<string>(() => "Hello task"); task.Start(); Console.WriteLine(task.Result);
above Console.WriteLine method prints the value “Hello task”
Exception handling in tasks
When the code in a task throws an exception we can handle it in the calling method.To handle the exception in the calling method we call Task.Wait.Calling Task.Wait propagates the exception to the calling code.In the calling method we can use try catch block to handle the exception.
When the exceptions are received in the calling code they are wrapped in AggregateException type which has an InnerExceptions property .We use this property to check the original exception thrown by the task.
var task = Task.Factory.StartNew(() => { throw new Custom(); }); try { task.Wait(); } catch (AggregateException ae) { Console.WriteLine("Exception caught-Details :{0}",ae.InnerException); }
Waiting for the Task
We can instruct the calling thread to wait for the task to finish execution using the wait() method.Once we call the wait() method on a task then the calling thread is not terminated until the task finishes execution.
To understand the use of wait() method execute the following code which doesn’t call the wait method.
Task task = new Task(() => { for (int i = 0; i < 10000; i++) Console.WriteLine(i); Console.ReadLine(); } ); task.Start(); //output: numbers in the range of 1-100
On executing the above code numbers are printed which are in the range of 1-100.It is because the calling thread terminates before the task can complete execution.
If we start the above task but this time call wait() method on the task instance ,all the numbers from 1 to 10,000 are printed.This is because the calling thread waits now for the task to finish execution.
Task task = new Task(() => { for (int i = 0; i < 10000; i++) Console.WriteLine(i); Console.ReadLine(); } ); task.Start(); task.Wait(); //output: All the numbers in the range of 1-10,000
The calling thread waits for the task until
- task completes execution
- task throws exception
There is an overload of wait() method in which we can specify the time out.So the calling thread waits until the task completes or the time out period elapses.
task.Wait(timeout);
Task Continuation
There are cases when we want to execute multiple tasks in a sequence.We can have 2 tasks and we want to execute the second task only when the first task finishes execution.We can easily implement this scenario using the ContinueWith() method.
In the following code firstTask executes first.Once the firstTask finishes execution secondTask is executed.
var firstTask= new Task(() => { Console.WriteLine("first task"); }); var secondTask = firstTask.ContinueWith(x => { Console.WriteLine("second task"); }); firstTask.Start(); Console.ReadLine();
So this was a basic introduction to Tasks in C# and and different ways for executing Tasks.
Leave a Reply