Exiting Tasks (advanced)
In the post “exiting Threads” (https://csharphardcoreprogramming.wordpress.com/2013/12/03/exiting-threads/) we were using a parameter class to define an exitFlag (boolean) and tell a thread when to end. There is a similar boolean for tasks supported by the Microsoft Framework. The class that contains this flag is called CancellationToken.
static void ArbitraryTask1(CancellationToken xCancellationToken) { while (!xCancellationToken.IsCancellationRequested) { Console.WriteLine(DateTime.Now.ToString("HH:mm:ss")); Thread.Sleep(1000); } Console.WriteLine("task says good-bye!"); } // static void CancelTask1() { CancellationTokenSource lCancellationTokenSource = new CancellationTokenSource(); CancellationToken t = lCancellationTokenSource.Token; Task lTask = Task.Factory.StartNew(() => ArbitraryTask1(t), t); Thread.Sleep(5000); lCancellationTokenSource.Cancel(); Thread.Sleep(1000); } //
The program starts a task, waits 5 seconds and then cancels the task. This is straight forward.
In the next example we exit a task by throwing an exception. This is a valid solution. But in general you should avoid exceptions as much as possible. As the name says it already: It is for exceptional situations and not for general solutions. If you use exceptions and catch() blocks frequently in your code, you will definitely slow down your performance. Throwing an exception is very time-consuming.
Nevertheless the TPL is extensively using exceptions. For instance in connection with PLINQ you will actually need to throw exceptions in order to properly report back to the user.
static void ArbitraryTask2(CancellationToken xCancellationToken) { ArbitraryTask1(xCancellationToken); xCancellationToken.ThrowIfCancellationRequested(); } // static void CancelTask2() { CancellationTokenSource lCancellationTokenSource = new CancellationTokenSource(); CancellationToken t = lCancellationTokenSource.Token; Task lTask = Task.Factory.StartNew(() => ArbitraryTask2(t), t); Thread.Sleep(5000); try { lCancellationTokenSource.Cancel(); lTask.Wait(); } catch (AggregateException e) { Console.WriteLine("Exception message: " + e.InnerExceptions[0].Message); } Console.ReadLine(); } //
And the following is important. In fact not throwing an exception causes unexpected behavior. Unless I misunderstood something here, this seems to be a serious issue.
static void CancelTask3() { CancellationTokenSource lCancellationTokenSource = new CancellationTokenSource(); CancellationToken lToken = lCancellationTokenSource.Token; Task lTask = Task.Factory.StartNew(() => ArbitraryTask1(lToken), lToken); lTask.ContinueWith((x) => { Console.WriteLine("task cancelled"); }, TaskContinuationOptions.OnlyOnCanceled); lTask.ContinueWith((x) => { Console.WriteLine("task completed"); }, TaskContinuationOptions.OnlyOnRanToCompletion); //Thread.Sleep(5000); lCancellationTokenSource.Cancel(); Console.ReadLine(); } //
example output:
task cancelled
The output is exactly what we expect. But now let’s uncomment “Thread.Sleep(5000);”. Suddenly we get something this:
example output:
18:07:37
18:07:38
18:07:39
18:07:40
18:07:41
task says good-bye!
task completed
The first 6 lines are fine. But then we get “task completed”. And all we did was adding one sleep() method in the code. If you have any idea what is causing it please leave a comment.
All I can say here is that throwing an exception solves the problem.
static void CancelTask3() { CancellationTokenSource lCancellationTokenSource = new CancellationTokenSource(); CancellationToken lToken = lCancellationTokenSource.Token; //Task lTask = Task.Factory.StartNew(() => ArbitraryTask1(lToken), lToken); // without throwing an exception Task lTask = Task.Factory.StartNew(() => ArbitraryTask2(lToken), lToken); // with throwing an exception lTask.ContinueWith((x) => { Console.WriteLine("task cancelled"); }, TaskContinuationOptions.OnlyOnCanceled); lTask.ContinueWith((x) => { Console.WriteLine("task completed"); }, TaskContinuationOptions.OnlyOnRanToCompletion); Thread.Sleep(5000); lCancellationTokenSource.Cancel(); Console.ReadLine(); } //
output example:
18:13:30
18:13:31
18:13:32
18:13:33
18:13:34
task says good-bye!
task cancelled
You can also comment the “Thread.Sleep(5000)” out. The returned task state does not change.
Btw. the equivalent of “ThrowIfCancellationRequested()” is
if (token.IsCancellationRequested) throw new OperationCanceledException(token);
And here is a quick example of using timeouts in case your tasks take longer than expected.
static void CancelTask4() { CancellationTokenSource lCancellationTokenSource = new CancellationTokenSource(); CancellationToken t = lCancellationTokenSource.Token; Task lTask1 = Task.Factory.StartNew(() => ArbitraryTask1(t), t); Task lTask2 = Task.Factory.StartNew(() => ArbitraryTask1(t), t); int i = Task.WaitAny(new Task[] { lTask1, lTask2 }, 5000); if (i == -1) { Console.WriteLine("tasks timed out"); lCancellationTokenSource.Cancel(); // you can comment this line out to see the change in behaviour if (!Task.WaitAll(new Task[] { lTask1, lTask2 }, 5000)) { Console.WriteLine("OMG, something went wrong again!"); } } Console.ReadLine(); } //
Posted on December 11, 2013, in Advanced, C#, Threading and tagged C#, CancellationToken, Exception handling, exiting tasks, Microsoft, Parallel Extensions, Source code, Task, Thread. Bookmark the permalink. 1 Comment.
Pingback: Events (part 3, advanced) | C# Hardcore Programming