Reputation: 7148
I have a class that strings together small chunks of IO work as Task
continuations. Each time a piece of work is received a new Task
is created, it is then added as a continuation to the LastCreatedTask
. I am trying to determine the proper way to cancel all of these Task
properly?
Here is my setup currently
private Task LastCreatedTask { get; set; }
private CancellationTokenSource TaskQueueTokenSource { get; set; }
public void ScheduleChunk(IOWorkChunk inChunk, int procTimeout)
{
Task ioChunkProcessTask = CreateProcessTask(inChunk, procTimeout);
LastCreatedTask.ContinueWith(
(t) => ioChunkProcessTask.Start(),
TaskContinuationOptions.ExecuteSynchronously);
LastCreatedTask = ioChunkProcessTask;
}
private Task CreateProcessTask(IOWorkChunk inChunk, int procTimeout)
{
// Create a TokenSource that will cancel after a given timeout period
var ProcessTimeoutTokenSource = new CancellationTokenSource(
TimeSpan.FromSeconds(procTimeout));
// Create a TokenSource for the Task that is a
// link between the timeout and "main" token source
var ProcessTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
TaskQueueTokenSource.Token,
ProcessTimeoutTokenSource.Token);
// Create a Task that will do the actual processing
Task ioChunkProcessTask = new Task(() =>
{
if(!ProcessTokenSource.Token.IsCancellationRequested)
inChunk.DoProcessing(ProcessTokenSource.Token);
}, ProcessTokenSource.Token);
return ioChunkProcessTask;
}
So in the function ScheduleChunk
a "chunk" of IO work (and a timeout) are passed to CreateProcessTask
which creates a Task
that will do the actual processing of the IO work. A CancellationToken
is passed to this Task
which is made by chaining two CancellationTokenSources
together.
The first is the "main" CancellationTokenSource
; I want to be able to simply call Cancel
on this source to cancel all of the Task
that are chained. The second source is one that will automagically cancel after some given period of time (this stops long running/stalled IO chunks).
Finally once the constructed Task
is returned to ScheduleChunk
is it added as a continuation to the LastCreatedTask
which is the last task the was added as a continuation. This in effect makes a chain of Task
that run in order one after the other.
1. Is my method above the proper way to cancel the chain of Task
? By calling Cancel
on the TaskQueueTokenSource
?
2. Is the use of the TaskContinuationOptions.ExecuteSynchronously
along with continuations the proper way to ensure that these Task
are executed in order one after the other?
3. Is there a way to specify that I would like the same thread from the underlying TPL ThreadPool
to work on this chain of Task
?
From what I can tell a new thread should not be created for each continuation point, though it is possible at some point that a new thread could pickup the chain.
Upvotes: 1
Views: 62
Reputation: 171246
The existing code does not work at all:
LastCreatedTask.ContinueWith((t) => ioChunkProcessTask)
This continuation merely returns a task whose result will be set to a constant object almost immediately. That's all it does.
Really, this code is structured awkwardly. This is much better:
async Task RunProcessing() {
while (...) {
await CreateProcessTask(...);
}
}
await
is made for sequencing async actions. await
can replace ContinueWith
most of the time.
The cancellation looks good. Maybe it can be simplified a bit but it's working fine.
Regarding 3, you should never need this. Thread affinity is a rare thing and to be avoided. There is no way to do this exactly as asked. Please elaborate what you want to achieve.
If you insist on using Task.Run, here's a sketch:
Task CreateProcessTask(IOWorkChunk inChunk, int procTimeout)
{
// Create a TokenSource that will cancel after a given timeout period
var ProcessTimeoutTokenSource = new CancellationTokenSource(
TimeSpan.FromSeconds(procTimeout));
// Create a TokenSource for the Task that is a
// link between the timeout and "main" token source
var ProcessTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
TaskQueueTokenSource.Token,
ProcessTimeoutTokenSource.Token);
return Task.Run(() => {
inChunk.DoProcessing(ProcessTokenSource.Token);
}, ProcessTokenSource.Token);
}
Upvotes: 2
Reputation: 86
Use a shared cancellation token that you pass to each task instead of creating a unique one for each task. When you cancel that token, all tasks using that token will then know to stop processing.
You edited after I answer, so I'll reply to your individual numbered questions:
1. Is my method above the proper way to cancel the chain of Task? By calling Cancel on the TaskQueueTokenSource?
According to msdn, best practices is to create one token, then passing that same token to each task.
2. Is the use of the TaskContinuationOptions.ExecuteSynchronously along with continuations the proper way to ensure that these Task are executed in order one after the other?
No, you Tasks are meant to be run in parallel, the best practices are instead to have the tasks that rely on ordering to be call each other down a chain, The first calling the second and so on. Alternatively you wait until the first task is complete before you start the second task.
3. Is there a way to specify that I would like the same thread from the underlying TPL ThreadPool to work on this chain of Task?
It is very unlikely that you you should be doing this, part of the purpose of the thread pool and the Task Asynchronous Programming (TAP) and TPL is to abstract the explicit threading away. You aren't guaranteed the thread that a task is ran on or even whether or not a new thread is generated for that task without a lot of work.
That said, if, for some reason you really do need to do this a custom task scheduler is the answer
Upvotes: 0