This post is based on one of the questions I answered on StackOverflow, in which the questioner wants to cancel the task when it's taking too long to respond, i.e., taking too much time in execution and returning the result. But, when I tried to provide the answer to that question, I found there is no direct way to cancel the task when making the call to Web Service or making the call to Database to get the data via third-party library ( XenAPI in my case) which is hanging up the application and not allowing it to proceed. To understand this, have a look at the below code.

  1. Var task = Task.Factory.StartNew(()=> CallWebServiceandGetData());  

The above line of code is creating the task which is making calls to the webservice to get the data. Now, the developer wants to write a code in such a way that if the task takes more than 10 seconds, it gets canceled. However, in TPL library, there is no way to cancel the task, i.e., there is no direct method or there is no other way to make this task cancel because task.Cancel() or task.Abort() like methods do not exist in TPL. The below post is about how a developer can really abort a task.

Aborting thread vs Cancelling task

What is the difference between aborting thread and cancelling task.

Aborting thread 

System.Treading is a library provided for threading prior to TPL library. (Just to note - old System.Threading is still part of .NET framework but TPL provides more control on Task which is wrapper around thread). In this library, to abort thread there is a method called Abort() available. With the help of this method, a developer can ask execution environment (CLR) to abort the thread. Below is an example code for the same.

  1. Thread newThread = new Thread(() => Console.WriteLine("Test"));  
  2. newThread.Start();  
  3. Thread.Sleep(1000);//sleeping main thread  
  4. newThread.Abort();main thread aborting newly created thread.  

Cancelling Task 

In the newer library, TPL (System.Threading.Tasks), there is no direct method which cancels or aborts the underlying thread. But there is a way to cancel a task by using CancellationTokenSource class which allows you to pass the CancellationToken as one of the input parameters when you create the task. (more on this is discussed below). Below is the code for cancelling the task.

  1. var source = new CancellationTokenSource();  
  2.           CancellationToken token = source.Token;  
  3.           Task.Factory.StartNew(() => {   
  4.             for(int i=0;i< 10000;i++)  
  5.             {  
  6.                 Console.WriteLine(i);  
  7.                 if (token.IsCancellationRequested)  
  8.                     token.ThrowIfCancellationRequested();  
  9.             }  
  10.           }, token);  
  11.           source.CancelAfter(1000);  

So, the above code makes use of CancellationTokenSource and CancellationToken provided by it. In the above code, CancellationTokenSource calls the method CancelAfter which sets taskCancellation flag. This flag is watched inside delegate via IsCancellationRequested property on CancellationToken. And once it sees in the for loop that IsCancellationRequested flag is true, it calls the ThrowIfCancellationRequested() method and cancels the thread.

So, in simple terms, abort thread allows the developer to abort executing thread and CancellationToken in new TPL library does the same thing, which is called cancelation of task. So basically, newer(TPL) and older(Threading) have different way to cancel/abort thread.

But one of the major differences between Abort() thread and Cancel task is that Abort() can leave application in an inconsistent state ( on Abort(), the system immediately aborts the thread, not allowing you to perform any operation to put application in consistent state ), especially when doing file operation or doing create/update operation , so it is better to take care when aborting thread or writing code in such a way that the application remains in consistent state. That is one of the reasons TPL come up with Cancellation mechanism, so those who write the code can watch cancellation flag and if it gets true, then they can write the code to put application in consistence state.

Returning to problem of Cancelling task

By reading the above section of “Cancellation Task”, one can say there is a provision to cancel task which in turn cancels the thread also and puts the system in consistent state, so it’s a better approach. But, if we now go back to the scenario where a task is created to fetch the data from webService or Database which is taking too much long time, the code will be like below with cancellation mechanism.

  1. var source = new CancellationTokenSource();  
  2. CancellationToken token = source.Token;  
  3. Task.Factory.StartNew(() => {   
  4.     try  
  5.     {  
  6.         //below is third party library(XenAPI) method   
  7.         HTTP_actions.put_import(…//parameter of method);  
  8.         //instead of this there can be database call to get data  
  9.         //which takes too much time   
  10.     }  
  11.     catch (HTTP.CancelledException exception)  
  12.     {  
  13.     }  
  14.     //execution never comes here till above method get complete             
  15.     if (token.IsCancellationRequested)  
  16.         token.ThrowIfCancellationRequested();  
  17.                 
  18. }, token);  
  19. source.CancelAfter(1000);  

In the above scenario, once the call is made to API method, it never comes back, so control of execution will not return to the application and the code which checks cancellation never get executed till the call returns. Which means the Task does not get cancelled even after 1000 ms and its cancellation flag is set to true for cancellation of task.

Above scenario is based on Third party API so it might be difficult to understand context. For easy understanding, let us have a look at the below code (just one change here, TaskCompletionSource is used to wrap the underlying task).

  1. static Task<string> DoWork(CancellationToken token)  
  2.         {  
  3.             var tcs = new TaskCompletionSource<string>();  
  4.   
  5.             //comment this whole this is just used for testing   
  6.             Task.Factory.StartNew(() =>  
  7.             {  
  8.                 //Simulate work (usually from 3rd party code)  
  9.                 for (int i = 0; i < 100000; i++)  
  10.                     Console.WriteLine("value" + i);  
  11.   
  12.               //execution never comes here till above for loop or          
  13.               //may be long execution /computation get completed             
  14.                if (token.IsCancellationRequested)  
  15.                     token.ThrowIfCancellationRequested();  
  16.   
  17.                 Console.WriteLine("Task finished!");  
  18.             },token);  
  19.             tcs.SetResult("Completed");  
  20.             return tcs.Task;  
  21.         }  
  22.   public static void Main()  
  23.         {  
  24.             var source = new CancellationTokenSource();  
  25.             CancellationToken token = source.Token;  
  26.             DoWork(token);  
  27.             source.CancelAfter(1000);  
  28.             Console.ReadLine();  
  29. }  

In the above code, instead of third-party code, I replaced it with the For loop (or consider long calculation task). Now, when execution is going on, the application cannot get a chance to read the cancellation flag which is set up by main thread i.e. from the main method. So, the application is not able to cancel the task until computation is over and control reaches the  point where cancellation flag check is done.

In both of the scenarios, the major problem is when log computation or long call is going on, the application cannot cancel the task. The Cancellation mechanism provided in TPL does not work and there, we need a solution to cancel this task another way.

Solution code is,

  1. class Program  
  2. {  
  3.     //capture request running that , which need to be cancel in case  
  4.     // it take more time   
  5.     static Thread threadToCancel = null;  
  6.     static async Task<string> DoWork()  
  7.     {  
  8.         var tcs = new TaskCompletionSource<string>();  
  9.         //comment this whole this is just used for testing   
  10.         await Task.Factory.StartNew(() =>  
  11.         {  
  12.             //Capture the thread  
  13.             threadToCancel = Thread.CurrentThread;  
  14.             //Simulate work (usually from 3rd party code)  
  15.             for (int i = 0; i < 100000; i++)  
  16.                  Console.WriteLine("value" + i);  
  17.            Console.WriteLine("Task finished!");  
  18.         });  
  19.         tcs.SetResult("Completed");  
  20.         return tcs.Task.Result;  
  21.     }  
  22.   
  23.     public static void Main()  
  24.     {  
  25.         var source = new CancellationTokenSource();  
  26.         CancellationToken token = source.Token;  
  27.         DoWork();  
  28.         //another task check for cancellation flag  
  29.         //cancels long running task by calling thread abort method   
  30.         Task.Factory.StartNew(() =>  
  31.         {  
  32.             while (true)  
  33.             {  
  34.                 if (token.IsCancellationRequested && threadToCancel != null)  
  35.                 {  
  36.                     threadToCancel.Abort();//abort long running thread  
  37.                     Console.WriteLine("Thread aborted");  
  38.                     return;  
  39.                 }  
  40.             }  
  41.         });  
  42.         //here 1000 can be replace by miliseconds after which you want to   
  43.         // abort thread which calling your long running method   
  44.         source.CancelAfter(1000);  
  45.         Console.ReadLine();  
  46.     }  
  47. }  

Comments in the code explain most of the things but let's go into detail about how it’s going to work. Following are the changes in code.

  1. Async/await used to make DoWork method asynchronous
  2. threadToCancel variable in code stores the reference of the thread of underlying Task by calling Thread.CurrentThread. This variable allows to cancel the thread of Task.
  3. One more Task gets created in main which keeps checking the Cancellation flag which is setup by source.CancelAfter(1000); after 1000 miliseconds.
  4. Task created in Main is running While loop with true, which keeps checking Cancellation flag true or not, once it gets true, this task executes the method threadToCancel.Abort(); to abort the underlying thread of long running task.

So, the real magic part in code is the reference to the underlying thread stored via Thread.CurrentThread. And separate tasks run in the main method to abort long-running task threads when cancellation flag sets to true by CancellationSoruce.

Pranay Rana

Pranay Rana

 

Hey, I am Pranay Rana, working as a Devloper in MNC.  Web development in Asp.Net with C# and MS sql server are the experience tools that I have had for the past 8.2 years now.For me def. of programming is : Programm... Read more

http://pranayamr.blogspot.com


'Knowledge' 카테고리의 다른 글

Working With Thread Local Storage (TLS) in C#  (0) 2018.04.18
Cross Thread Operations in C#  (0) 2018.04.18
Task And Thread In C#  (0) 2018.04.18
[.NET] 텍스트 파일에서 읽기  (0) 2018.04.15
[SQLite3] 튜토리얼 사이트  (0) 2018.04.15

+ Recent posts