.NET Articles

Task in c#


Asynchronous programming or Parallel Programming is used for run methods in parallel so that execution performance will be faster.

Let us take one example scenario, there is a registration process which takes the User Name as input and performs several validations then completes the registration. Assume one registration process takes 1 minute for 1 user and for 100 users it will take 100*1 =100 minutes. Using asynronous program, you can execute these 100 User registrations in parallel so that it would finish 100 user registrations in around 1 minute which means execution time has been reduced drastically.

See the below example code and output to get clear idea then understand in depth.

using System;
using System.Threading.Tasks;

partial class Program
{
    static void Main(string[] args)
    {         
        string[] Users = new string[] { "Steve", "Ram", "Krishna" };

        for (int i = 0; i< Users.Length;i++)
        {
            AsyncRegisterUser(Users[i]);
        }
        Console.WriteLine("Process is finished!");
        Console.ReadLine();
    }

    public async static void AsyncRegisterUser(string User)
    {
        await RunTaskRegisterUser(User);            
    }

    public static Task RunTaskRegisterUser(string User)
    {
        return Task.Run(() => RegisterUser(User));
    }
        
    public static void RegisterUser(string User)
    {
        System.Threading.Thread.Sleep(500);
        Console.WriteLine("Start of registration process for user - {0} ", User);
        System.Threading.Thread.Sleep(500);
        Console.WriteLine("Inprogress of registration process for user - {0} ", User);
        System.Threading.Thread.Sleep(1000);
        Console.WriteLine("End of registration process for user - {0} ", User);            
    }
}
        

Output:

C# Task Output
Below are the key points in writing asynchronous program.
  • To run method as asynchronous, method should be awaitable. The class ” Task” is a awaitable so return type of method should be of class “Task” in order to run in parallel. In example the method “RunTaskRegisterUser” has a return type “Task” so it is executing asynchronously. Task is a generic class if you want to return some datatype then declare type like “Task<String>”, this is for string data type. If return nothing just mention as “Task” as in our example.
  • In output, the last statement of program "Process is finished!" is executed first because the method “AsyncRegisterUser” is executed in asynchronously.
  • If method return type is used as “Task” then append the keyword “await” to calling Task -method, example "await RunTaskRegisterUser(User);".
  • The method which is using “await” keyword should have “async” keyword in method declaration example “public async static void AsyncRegisterUser(string User)”
  • When the statement with “await” is executed then method will be suspended and will execute as separate thread. To understand better, analyze the example.
  •     public async static void AsyncRegisterUser(string User)
        {
            await RunTaskRegisterUser(User);
            Console.WriteLine("Welcome to {0}", User);
        }
                    
    When await RunTaskRegisterUser(User); executed, then method “AsyncRegisterUser” will be suspended and will run as a separate thread.

Task with Return type and exception handling

In above example, the async method of registering a user is not returning any value but if there is a requirement such as async method should return a time when user registration process (method execution) is completed. The below example shows how to return a value from asynchronous method and how to handle exceptions of async methods.

First execute below code and see the output then discuss in depth.

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
partial class Program
{
    static void Main(string[] args)
    {
        string[] Users = new string[] { "Steve", "Ram", "Krishna" };
        List<Task> taskCollection = new List<Task>();
        try
        {
            for (int i = 0; i < Users.Length; i++)
            {
                taskCollection.Add(AsyncRegisterUser(Users[i]));
            }

            //To wait all tasks to be completed
            Task.WaitAll(taskCollection.ToArray());
            foreach (Task<String> _task in taskCollection)
            {
                Console.WriteLine(_task.Result);
            }
            Console.WriteLine("Process is finished!");
        }
        //Exception handling is different in Async Programming. Must be written like below
        catch (AggregateException ae)
        {
            foreach (var item in ae.Flatten().InnerExceptions)
            {
                Console.WriteLine("Something went wrong :" + item.Message);
            }
        }
        Console.ReadLine();
    }
    public async static Task<String> AsyncRegisterUser(string User)
    {
        return await RunTaskRegisterUser(User);
    }
    public static Task<String> RunTaskRegisterUser(string User)
    {
        return Task.Run(() => RegisterUser(User));
    }
    public static string RegisterUser(string User)
    {
        //To test exception handling just uncomment below line
        //throw new Exception("Invalid User : " + User);
        System.Threading.Thread.Sleep(500);
        Console.WriteLine("Start of registration process for the user - {0} ", User);
        System.Threading.Thread.Sleep(500);
        Console.WriteLine("Inprogress of registration process for the user - {0} ", User);
        System.Threading.Thread.Sleep(1000);
        Console.WriteLine("End of registration process for the user - {0} ", User);
        return "Registration completion time of User " + User + " at " + DateTime.Now.ToString();
    }
}
        

Output:

C# Task Output
Code Analysis
  • In below code,
    
                            public static Task<String> RunTaskRegisterUser(string User)
                                {
                                    return Task.Run(() => RegisterUser(User));
                                }
                        

    The line “return Task.Run(() => RegisterUser(User));” Returns string value and method declaration should be “public static Task<String> RunTaskRegisterUser(string User)” to indicate Task returns “String” type. Since this method executes asynchronously return type should be written as “Task<String>”

  • Any method having the “async” keyword in method declration, return type should be mentioned in Task parentheses like “Task<String>” as shown in below method declaration.
     public async static Task<String> AsyncRegisterUser(string User)
  • The below statement is used to wait for all tasks to be completed
    “Task.WaitAll(taskCollection.ToArray());”

    Now we have the tasks collection “taskCollection” with asyn methods return values.

    The below method is used for display all return values from the asyc methods.

                            foreach (Task<String> _task in taskCollection)
                                {
                                    Console.WriteLine(_task.Result);
                                }
                        

    Task. Result method is used for return value of method.

  • Exception handling is different in Tasks compare to normal exception handling. In catch block use the class “AggregateException” that will have all the exception raised during execution of the async methods. Then read the all exceptions using foreach class as shown below.
                            {        
                            catch (AggregateException ae)
                                {
                                    foreach (var item in ae.Flatten().InnerExceptions)
                                    {
                                        Console.WriteLine("Something went wrong :" + item.Message);
                                    }
                                }
                        

    If you want to see how exception works in Tasks then uncomment the code in method “RegisterUser” that is,

     throw new Exception("Invalid User : " + User);

    Then output will be,

    C# Task Output

Task cancellation

See below example code how to cancel a Task with screenshot. Then analyze the code


using System;
using System.Threading.Tasks;
using System.Threading;
class Program
{
    static void Main(string[] args)
    {
        //Create cancellation token
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken ct = cts.Token;

        Console.WriteLine("Press - 0 to exit the task");
        Task.Factory.StartNew(() => CountDown(ct));

        //Register() executes delegate method TaskCancelNotification()  when cts.Cancel() called
        ct.Register(() => TaskCancelNotification());
        //Check if user entered '0' to exit the task
        if (Console.ReadKey().KeyChar == '0')
        {
            Console.WriteLine();
            cts.Cancel();
        }
        Console.ReadLine();
    }
    public static void TaskCancelNotification()
    {
        Console.WriteLine("Notification of task cancellation!");
    }
    public static void CountDown(CancellationToken ct)
    {
        for (int i = 50; i >= 1; i--)
        {
            System.Threading.Thread.Sleep(2000);
            //IsCancellationRequested set to true when CancellationTokenSource.Cancel() called
            if (ct.IsCancellationRequested)
            {
                Console.WriteLine("Requested for cancellation!");
                break;
            }
            Console.WriteLine(i);
        }
    }
}

           

Output:

C# Task Output
Code Analysis
  • Create a Cancellation Token by using following 2 lines of code.
                            CancellationTokenSource cts=new CancellationTokenSource(); 
                           CancellationToken ct=cts.Token;                     
                        

    "CancellationToken ct=cts.Token;" statement creates Cancellation Token from property of CancellationTokenSource class then same Token passes to the method "CountDown" which is executing as async and "ct.IsCancellationRequested" property sets to True and exit when CancellationTokenSource.Cancel() executes(when user Presses key 0)

  • From below code,
                    ct.Register(() => TaskCancelNotification());
                    
    This codes Registers a method to the delegate and will be call back when method CancellationTokenSource.Cancel() executes.