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();
} //
Advertisements

About Bastian M.K. Ohta

Happiness only real when shared.

Posted on December 11, 2013, in Advanced, C#, Threading and tagged , , , , , , , , . Bookmark the permalink. 1 Comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: