0

I am trying to cancel a time consuming task after a certain milliseconds and for my case, I think CancellationToken.Register method best fits compared to other approaches with constant polling or WaitHandle. CancellationToken.Register method will help me define a delegate where I plan to take the task to cancelled state and stop further execution of the task; this delegate will be invoked when the task is cancelled (certain milliseconds later as per my goal). Here is the test code I have which I intend to extend for multiple tasks and with nested tasks later:

List<Task> tasks = new List<Task>();
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;

Task t1 = Task.Factory.StartNew(() =>
{
    // check cancellation token before task begins
    if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested();

    // register a callback to handle cancellation token anytime it occurs
    cancellationToken.Register(() =>
    {
        Console.WriteLine("Task t1 cancelled");
        cancellationToken.ThrowIfCancellationRequested();
    });

    // simulating a massive task; note, it is not a repeating task
    Thread.Sleep(12000);
}, cancellationToken);

tasks.Add(t1);

try
{
    // cancel token after 3000 ms + wait for all tasks
    tokenSource.CancelAfter(3000);
    Task.WaitAll(tasks.ToArray());

    // OR wait for all tasks for 3000 ms and then cancel token immediately
    //Task.WaitAll(tasks.ToArray(), 3000);
    //tokenSource.Cancel();
}
catch (AggregateException e)
{
    Console.WriteLine("\nAggregateException thrown with the following inner exceptions:");
    // Display information about each exception. 
    foreach (var v in e.InnerExceptions)
    {
         if (v is TaskCanceledException)
              Console.WriteLine("   TaskCanceledException: Task {0}",                                              ((TaskCanceledException)v).Task.Id);
         else
              Console.WriteLine("   Exception: {0}", v.GetType().Name);
    }
    Console.WriteLine();
}
finally
{
    tokenSource.Dispose();
}

I am, however, facing an issue with exception handling during execution of the callback method inside cancellationToken.Register. The call to cancellationToken.ThrowIfCancellationRequested() gives me exception "OperationCanceledException was unhandled by user code" followed by "AggregateException was unhandled". I have read about VS settings with unchecking User-unhandled exception for the first OperationCanceledException exception but my application terminates after second AggregateException exception; the try..catch block for Task.WaitAll does not seem to handle this.

I tried to enclose cancellationToken.ThrowIfCancellationRequested() in a try..catch block but the problem with this approach was that the task continued with the remaining steps which I do not desire. I do not see this behaviour with polling approach.

// poll continuously to check for cancellation instead of Register
// but I do not want my massive task inside this repeating block
while (true)
{
    if (cancellationToken.IsCancellationRequested)
    {
        Console.WriteLine("Task t1 Canceled.");
        cancellationToken.ThrowIfCancellationRequested();
    }
}

What am I doing wrong with the CancellationToken.Register approach?

RUSH
  • 1
  • 5

1 Answers1

0

The error you see is exactly because you didn't wrap the ThrowIfCancellationRequested inside a try-catch block.

In my opinion it really depends on what you're doing in place of that Sleep(). Ending the task in a cooperative way like

while(!cancellationToken.IsCancellationRequested)
{
    // Do stuff

    // Also check before doing something that may block or take a while
    if(!cancellationToken.IsCancellationRequested)
    {
        Stream.Read(buffer, 0, n);
    }
}

should be the best way to stop it. If you really need to stop it no matter what, I would do wrap it in another task

Task.Run(() => 
{
    // Simulate a long running task
    Thread.Sleep(12*1000);
}, cancellationToken);

(tested and working) This way you should not see any Exception Unhandled thrown. Also, you might want to take a look at this: How do I abort/cancel TPL Tasks?

GodHand
  • 1
  • 3