Tuesday, October 10, 2017

Task Cancellation

Intro

When doing speculative work (starting multiple thread and only caring about the first result that comes back) these thread can take up valuable resources such as cpu or are long running, etc. We can just let these threads run to completion, but that won't free up the resources until they are done. Ideally once we have the result we want to cancel the threads once we don't need them to run anymore.

Cooperative Model

Creator passes a cancellation token, starts the task, later signals cancel, etc
Task monitors the token, if it is cancelled then it performs the cleanup and throws an exception.

Status on Task is set to "Canceled"


var cts = new CancellationTokenSource();
var token = ctx.Token;

Task t = Task.Factory.StartNew( () =>
{
try{
while (...)
{
// check for cancellation at start of each iteration
if (token.IsCancellationRequested)
{
// clean up
// NB: Make sure this exception bubbles up (as shown here) to the caller.
token.ThrowIfCancellationRequested();
}

// do the stuff would normally do here
...
}
}
catch (OperationCancelledException)
{
throw;
}
catch (Exception ex)
{
// handle exception
}
}, 
token // allows .NET to set status appropriately
);

// this may be invoked by a menu, keystroke, or other reason.
// This will trigger the canelling of the task.
// This would typically not be in the same location as the rest of the code here.
if (some condition occurs) {
cts.Cancel();
cts = new CancellationTokenSource(); // If need to re-run
}

try 

t.Wait(); // throws exception if cancelled
}
catch (AggregateException ae) {
ae = ae.Flatten();
foreach(var ex in ae.InnerExceptions)
{
if (ex is OperationCancelledException)
// do something (ignoring it now)
else
// handle exception (ex.Message will get error message)
}

}

Note: By passing the same token to several tasks will cause all the tasks to be cancelled when cts.Cancel() is called.

Caveat: Once Cancel() has been called on a task, the token passed to the Task cannot be used again once its status has been changed to Canceled. If you re-run the task you will need to create a new token as shown above. A good place is right after Cancel() is called.

Alternative approach

Use a global variable and check it to see if the task has been canceled. The trick in either implementation is to get the right level of polling. Too often will have performance implications and too little and cancelling won't be quick enough and waste resources.

Reference

Content is based on Pluralsight video called Introduction to Async and Parallel Programming in .NET 4 by Dr. Joe Hummel. 

No comments: