Cancellation token in Task constructor: why?
Passing a CancellationToken
into the Task
constructor associates it with the task.
Quoting Stephen Toub's answer from MSDN:
This has two primary benefits:
- If the token has cancellation requested prior to the
Task
starting to execute, theTask
won't execute. Rather than transitioning to
Running
, it'll immediately transition toCanceled
. This avoids the
costs of running the task if it would just be canceled while running
anyway.- If the body of the task is also monitoring the cancellation token and throws an
OperationCanceledException
containing that token
(which is whatThrowIfCancellationRequested
does), then when the task
sees thatOperationCanceledException
, it checks whether theOperationCanceledException
's token matches the Task's
token. If it does, that exception is viewed as an acknowledgement of
cooperative cancellation and theTask
transitions to theCanceled
state (rather than theFaulted
state).
Why can a Task be associated with a CancellationToken?
The documentation for that particular overload of Task.Run
explicitly and clearly states:
A cancellation token that can be used to cancel the work if it has not yet started.
Run(Action, CancellationToken)
does not passcancellationToken
toaction
.
Tasks are not "created with" or "associated with" cancellation tokens.
So it's only used as an early out. If you give to Task.Run
a token that's already cancelled, nothing is scheduled and an already cancelled task is returned. Similarly, if it becomes cancelled by the moment the task is ready to run, the resulting task will get cancelled.
Cancellation in .NET is always cooperative, not preemptive: the task's function is responsible for checking the token itself once it has begun.
What is the use of passing CancellationToken to Task Class constructor?
UPDATE:
The following msdn question describes the reason:
Passing a token into StartNew associates the token with the Task.
This has two primary benefits:
If the token has cancellation
requested prior to the Task starting to execute, the Task won't
execute. Rather than transitioning to Running, it'll immediately
transition to Canceled. This avoids the costs of running the task if
it would just be canceled while running anyway.If the body of the
task is also monitoring the cancellation token and throws an
OperationCanceledException containing that token (which is whatThrowIfCancellationRequested
does), then when the task sees that OCE,
it checks whether the OCE's token matches the Task's token. If it
does, that exception is viewed as an acknowledgement of cooperative
cancellation and the Task transitions to the Canceled state (rather
than the Faulted state).
Passing cancellation token to calling method VS task constructor?
The first passes a token to your method, where you can do what you want with it. The second passes the token to Task.Run
that associates the task with that token.
Since cancellation in .NET is cooperative Task.Run
can only cancel your task if it hadn't started executing yet (which isn't that useful) and your method can only check the token from time to time and throw if cancellation was requested but that will mark the task as faulted instead of cancelled.
For a complete solution you should actually do both:
var task = Task.Run(() => LongTask(1000000, cancellationToken), cancellationToken);
That way the task is associated with the token and you can check the token for cancellation.
Passing cancellation token to Task.Run seems to have no effect
The Task.Run
has many overloads. The case of the t1
is peculiar because of the infinite while
loop.
var t1 = Task.Run(() =>
{
while (true)
{
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
};
});
The compiler has to choose between these two overloads:
public static Task Run(Action action);
public static Task Run(Func<Task> function);
...and for some unknown to me reason it chooses the later. Here is the implementation of this overload:
public static Task Run(Func<Task?> function, CancellationToken cancellationToken)
{
if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
// Short-circuit if we are given a pre-canceled token
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
// Kick off initial Task, which will call the user-supplied function and yield a Task.
Task<Task?> task1 = Task<Task?>.Factory.StartNew(function, cancellationToken,
TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
// Create a promise-style Task to be used as a proxy for the operation
// Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown
// as faults from task1, to support in-delegate cancellation.
UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1,
lookForOce: true);
return promise;
}
The important detail is the lookForOce: true
. Let's look inside the UnwrapPromise
class:
// "Should we check for OperationCanceledExceptions on the outer task and interpret them
// as proxy cancellation?"
// Unwrap() sets this to false, Run() sets it to true.
private readonly bool _lookForOce;
..and at another point below:
case TaskStatus.Faulted:
List<ExceptionDispatchInfo> edis = task.GetExceptionDispatchInfos();
ExceptionDispatchInfo oceEdi;
if (lookForOce && edis.Count > 0 &&
(oceEdi = edis[0]) != null &&
oceEdi.SourceException is OperationCanceledException oce)
{
result = TrySetCanceled(oce.CancellationToken, oceEdi);
}
else
{
result = TrySetException(edis);
}
break;
So although the internally created Task<Task?> task1
ends up in a Faulted
state, its unwrapped version ends up as Canceled
, because the type of the exception isOperationCanceledException
(abbreviated as oce
in the code).
That's a quite convoluted journey in the history of TPL, with methods introduced at different times and frameworks, in order to serve different purposes. The end result is a little bit of inconsistency, or nuanced behavior if you prefer to say it so. A relevant article that you might find interesting is this: Task.Run vs Task.Factory.StartNew by Stephen Toub.
Use a cancellation Token to cancel multiple Tasks
Use a CancellationTokenSource, in a somewhat simplified example:
CancellationTokenSource cts = new CancellationTokenSource();
async void Cancel_Click(object sender, EventArgs e)
{
cts.Cancel();
}
async void btnStart_Click(object sender, EventArgs e)
{
try{
cts = new CancellationTokenSource();
var token = cts.Token;
var t1 = Task.Run(() => Start(token));
var t2 = Task.Run(() => Start(token));
await Task.WhenAny(new []{t1, t2});
}
catch(OperationCancelledException){
// Handle canceled
}
catch(Exception){
// Handle other exceptions
}
}
void Start(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
// Do work
Thread.Sleep(100);
}
}
When the button is clicked it will first create a new cancellationTokenSource, then start two tasks on background threads, both using the token from the newly created token source. When the cancelButton is pressed the token will be set into canceled-state, and the next time each background thread calls ThrowIfCancellationRequested they will throw an exception. This will put the task into a canceled-state, and awaiting this will throw an operationCancelledException that need to be caught. Note that when awaiting multiple tasks you might get an aggregateException that wraps multiple exceptions and need to be unpacked.
Can cancellation token be used at tasks method within?
Yes, you can easily pass the same token onto StartSomething
and exceptions from it will bubble up to Control
and cancel the task. If you don't then it will keep running even if the CancellationTokenwas cancelled until it returns control to
Control` that observes the token:
void StartSomething(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested(); // Will cancel the task.
// ...
}
}
Keep in mind though that token.ThrowIfCancellationRequested()
will raise exception and the task will be canceled while !token.IsCancellationRequested
will simply complete the task without marking it as canceled.
Task.Run and CancellationToken. How does cancellation work in this scenario?
It seems that if the compiler can know at compile time that the task will always throw OperationCanceledException
is will end in Canceled
state, otherwize it will end in Faulted
.
void Main()
{
CancellationTokenSource cts = new CancellationTokenSource(1);
cts.Cancel();
var token = cts.Token;
var task = Task.Run(() =>
{
//if(GetTrue()) //Faulted
if(true) //Canceled
throw new OperationCanceledException();
});
Thread.Sleep(50);
Console.WriteLine(task.Status);
}
bool GetTrue()
{
return true;
}
But if you send in the same token to the Task.Run
as you throw inside the task it will end in Canceled
.
void Main()
{
CancellationTokenSource cts = new CancellationTokenSource(10000);
var token = cts.Token;
var task = Task.Run(() =>
{
cts.Cancel();
token.ThrowIfCancellationRequested();
}, token);
Thread.Sleep(50);
Console.WriteLine(task.Status);
}
Related Topics
Value of Type 'T' Cannot Be Converted To
Validateantiforgerytoken Purpose, Explanation and Example
What Is the Simplest Way to Get Indented Xml with Line Breaks from Xmldocument
C# Dictionary - One Key, Many Values
Creating a Comma Separated List from Ilist<String> or Ienumerable<String>
Difference Between Convert.Tostring() and .Tostring()
A Method to Count Occurrences in a List
What Are Independent Associations and Foreign Key Associations
Running Scripts in HTMLagilitypack
Opening New Window in Mvvm Wpf
Hide Tabcontrol Buttons to Manage Stacked Panel Controls
Is There Any Benefit to This Switch/Pattern Matching Idea
How to Read and Write from the Serial Port