Pranay Rana: TPL : Find Failed Task

Monday, November 27, 2017

TPL : Find Failed Task

TPL - Task paralle library is new libaray added by microsoft in .net framework 4.0. This libaray allows one to create task i.e Thread, which means multi threading application. Task created by this libarary allows greater control on task(thread) which is very diffcult with previous Threading library, one of them is exception handling specially when one use ThreadPool for doing work. Below post is about handling exception in TPL and about how to find which of task failed when multiple task excuted.

Exception Handling : Single Task (synchronous)

Exception handling is very easy when Task(thread) creaed by TPL. But to find out task failed or executed without exception one must need to use "Wait()" method or need use "Result" property. Below is example of same.

 try
 {
     //below throws divide by zero exception
     var task = Task.Factory.StartNew(() => { return 10 / 0;});
     task.Wait();//or use task.Result
     //or there may be multiple taks 
     // example
     // Task[] task = new Task[3](); 
     // task[1] = Task.Factory.StartNew(() => { return 10 / 0;});
     // task[2] = Task.Factory.StartNew(() => { return 10 / 0;});
     // task[3] = Task.Factory.StartNew(() => { return 10 / 0;}); 
     //Task.WaitAll(task); //for this multiple senario we need Aggregate exception.
 }
 catch (AggregateException ex)
 {
     Console.WriteLine(ex.InnerException.ToString());
 } 

above code throw DivideByZeroException. and same get printed as output of the above code. One thing to note here is it throws AggregateException exception, so one need to go to InnerException for getting exact exception message.AggregateException is need because in real time senario there might be running and if all throws exception than there need to be structure like AggregateException who stores excpetion of all task.

Exception Handling : Single Task (asynchronous)

Below is same code with asynchronous.

public static async void CallDivide()
{
  try
  {
     var task = Task.Factory.StartNew(() => Divide(0));
     await task;
  }
  catch (Exception ex)
  {
     Console.WriteLine(ex.ToString());
  }
}  

it will give output same as previous one. One thing to note here there is no need to use AggregateException as asynchronous as when task fails it exception get unwarped and get throw to captured context or on thread which is going to execute continuation. If there are multiple task than only first failed task exception get reported as asynchronous unwrap AggregateException and throw first one.

So there is not problem when you are execution one task and it fails , in that case you know which one failed. But actual problem occurs when you are executing multiple task and one of task fails, then there is no way which one failed.

Exception Handling : Multiple Task (synchronous)

To undestand this let go throgh below code

 public static void Main(string[] args)
 {
    List<int> lstInt = new List<int>(){1,11,4,3,0,7,0};
    List<Task<string>> tasks = new List<Task<string>>();
    foreach(int i in lstInt)
    {
        var task =Task.Factory.StartNew((obj)=> Divide(i),"Task " + i);
        tasks.Add(task);
    }
    try
    {
        Task.WaitAll(tasks.ToArray());
    }
    catch(AggregateException e)
    {
        for (int j = 0; j < e.InnerExceptions.Count; j++)
        {
            Console.WriteLine("\n--------------------\n{0}", e.InnerExceptions[j].Message);
        }
    }
    var failedTask = tasks.Where(t=> t.IsFaulted);
  }
        
  public static string Divide(int i)
  {
      var val = 10/i;
      Console.WriteLine(val);
      return "Value : " + i +" : " + val;
  }

Above code output is :

10
0
3
2
1

--------------------
Attempted to divide by zero
Attempted to divide by zero.

Exception Handling : Multiple Task (asynchronous)

Same output will be there when you try below asynchronous task based approch. But asynchronous only prints first failed task exception as asynchronous unwrap received AggregateException and report first one only.

public static async void CallDivide()
{
    List<Task<string>> tasks = new List<Task<string>>();
            try
            {
                List<int> lstInt = new List<int>() { 1, 11, 4, 3, 0, 7, 0 };
                
                foreach (int i in lstInt)
                {
                    var task = Task.Factory.StartNew((obj) => Divide(i), "Task " + i);
                    tasks.Add(task);
                }

                await Task.WhenAll(tasks.ToArray());
            }
            catch (Exception ex)
            {
                var exceptions = tasks.Where(t => t.Exception != null).Select(t => t.Exception);
                Console.WriteLine(ex.ToString());
            }
}
public static string Divide(int i)
{
    var val = 10 / i;
    Console.WriteLine(val);
    return "Value : " + i + " : " + val;
}

Above code output is :

10
0
3
2
1

--------------------
Attempted to divide by zero
 ///Attempted to divide by zero.- will not come in case of asynchronous

By the output you get to know one task failed, but you dont know which task failed or in simple term you dont know for which input my method failed when data coming from database or some external soruce(means right now we have static list of interger for example in real application this list of int is may be coming from via argument or via database).
Or imagine senario where you are querying multiple database via multiple task and one of the database query have problem and thro exception i.e. one of the task fails. In that senario you like to know which database failed.

Solution

Solution for this i.e. to find which task failed easy way is to attach "State" with task. When you attache State with Task you will get state value when you access property called "AsyncState". Below is line of code which shows how you can attach sate with task. To attach state one(developer) just need to pass one more argument after called function, which is "Task " + i in in this case.

var task = Task.Factory.StartNew((obj) => Divide(i), "Task " + i);
 
Below is full code how this solution works

Find Faild Task : Multiple Task (synchronous)

 static  void Main(string[] args)
        {
            Console.ReadLine();
            List<int> lstInt = new List<int>() { 1, 11, 4, 3, 0, 7, 8 };
            List<Task<string>> tasks = new List<Task<string>>();
            foreach (int i in lstInt)
            {
                var task = Task.Factory.StartNew((obj) => Divide(i), "Task with input " + i);
                tasks.Add(task);
            }
            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch 
            {
                   var failedTask = tasks.Where(t => t.IsFaulted);
                   foreach (var t in failedTask)
                   {
                      Console.WriteLine(t.AsyncState.ToString());
                       Console.WriteLine(t.Exception.InnerException.ToString());
                   }
            }
            
            Console.ReadLine();
        } 

Find Faild Task : Multiple Task (asynchronous)

  public static async void CallDivide()
        {
            List<Task<string>> tasks = new List<Task<string>>();
            try
            {
                List<int> lstInt = new List<int>() { 1, 11, 4, 3, 0, 7, 8 };

                foreach (int i in lstInt)
                {
                    var task = Task.Factory.StartNew((obj) => Divide(i), "Task with input" + i);
                    tasks.Add(task);
                }

                await Task.WhenAll(tasks.ToArray());
            }
            catch 
            {
                var failedTask = tasks.Where(t => t.Exception != null);
                foreach (var t in failedTask)
                {
                    Console.WriteLine("Failed Task is : " + t.AsyncState.ToString());
                    Console.WriteLine("Failed Task Excetption is : " + t.Exception.InnerException.Message);
                };
            }
        } 

output of above code
10
0
2
1
1
3
Failed Task is : Task with input 0
Failed Task Excetption is : Attempted to divide by zero.

both of the way will give you same output. But if you see output caefully , you see Task with input 0 failed. Here i just attached input but in real time senario like when querying datase you can attach name of database or you can also give some meaning ful vaue in state or even you can pass object as state. But point here is you can get to know which task failed when you attach state with Task.

No comments:

Post a Comment