Pls look at this code, running on .Net Core 2.0:
var src = new CancellationTokenSource(5000);
var token = src.Token;
var responseTask = await Task.Factory.StartNew(async () =>
{
//Uncomment bellow to reproduce locally
//await Task.Delay(60000);
return await BadSDK.OperationThatDoesNotReceiveCancellationToken();//takes around 1 min
}, token);
var response = await responseTask;
My issue here is that the await is always awaiting the very long sdk call, instead of waiting the 5sec.
What am I doing wrong? Where is my understanding wrong?
Edit1: This code behaves as expected:
var src = new CancellationTokenSource(5000);
var token = src.Token;
var responseTask = Task.Factory.StartNew(() =>
{
var task = BadSDK.OperationThatDoesNotReceiveCancellationToken();
task.Wait(token);
cancellationToken.ThrowIfCancellationRequested();
return task.Result;
}, token);
meaning, after 5 seconds, a exception is thrown
The problem is that the cancellation token pattern expects the task to check the token and exit or throw an error when the token has expired. Well written tasks will periodically check to see if cancellation is canceled and then the task can do any cleanup necessary and return gracefully or throw an error.
As you demonstrated, BadSDK.OperationThatDoesNotReceiveCancellationToken doesn't accept a CancellationToken and thus won't take any action based on the token. It doesn't matter if the token automatically requests cancellation via a timeout, or if the request is issued in some other matter. The simple fact is that BadSDK.OperationThatDoesNotReceiveCancellationToken simply isn't checking it.
In your Edit1, the CancellationToken is passed to Wait which does watch the token and will exit when cancellation is requested. This does not mean however that task was killed or stopped, it only stopped waiting for it. Depending on what you intend, this may or may not do what you want. While it does return after 5 seconds, the task will still continue running. You could even wait on it again. It may be possible to then kill the task but in practice, it can be a very bad thing to do (See Is it possible to abort a Task like aborting a Thread (Thread.Abort method)?)
The StartNew overload you used is a source of endless confusion. Doubly so, since its type is actually StartNew<Task<T>> (a nested task) rather than StartNew<T>.
A token on its own does nothing. Some code somewhere has to check the token, and throw an exception to exit the Task.
Official documentation follows this pattern:
var tokenSource = new CancellationTokenSource();
var ct = tokenSource.Token;
var task = Task.Run(() =>
{
while (...)
{
if (ct.IsCancellationRequested)
{
// cleanup your resources before throwing
ct.ThrowIfCancellationRequested();
}
}
}, ct); // Pass same token to Task.Run
But if you're anyway checking the token, and maybe throwing an exception why do you need to pass in the token in the first place, and then use the same token inside the closure?
The reason is that the token you pass in is what's used to move the Task to a cancelled state.
When a task instance observes an OperationCanceledException thrown by
user code, it compares the exception's token to its associated token
(the one that was passed to the API that created the Task). If they
are the same and the token's IsCancellationRequested property returns
true, the task interprets this as acknowledging cancellation and
transitions to the Canceled state.
P.S.
If you're on .Net Core or 4.5+, Task.Run is preferred to the factory approach.
You are passing the task the cancelation token token, but you don't specify what to do with the token within the async method.
You'd probably want to add token.ThrowIfCancellationRequested(); in the method, perhaps conditioned by token.IsCancellationRequested. This way the task will be canceled if src .Cancel() is called.
Related
The code discussed here is written in C# and executed with .netcore 3.1
I have the following piece of code, which starts a workload in the background without awaiting for it to complete (fire and forget):
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, token);
}
There are three important points:
the background work is started in a fire and forget manner. I'm not interested in awaiting its completion
the provided cancellation token is important and the background work must listed to incoming cancellation requests
the provided resource (IAsyncDisposable) must be released as soon as possible, regardless of the outcome of the background work. In order to release the resource a call to DisposeAsync is required.
The problem with this code is that the cancellation token is passed to Task.Run invokation. If the token is canceled before the execution of the async delegate starts, the async delegate is never executed and so the finally block is never executed. By doing so the requirement of releasing the IAsyncDisposable resource is not met (basically, DisposeAsync is never called).
The simplest way to solve this issue is not providing the cancellation token when Task.Run is invoked. That way, the async delegate is always executed and so the finally block is executed too. The code inside the async delegate listens to cancellation requests, so the requirement of cancel the execution is met too:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, CancellationToken.None);
}
I'm asking myself whether the release of the IAsyncDisposable resource should, instead, be delegated to a continuation task. The code refactored by using this approach is the following:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
},
token).ContinueWith(async _ =>
{
// release the IAsyncDisposable resource here, afte the completion of the antecedent task and regardless
// of the antecedent task actual state
await resource.DisposeAsync();
});
}
I'm not really familiar with ContinueWith gotchas, so my questions are the following:
do I have the guarantee that the continuation is always executed, even if the cancellation token is canceled before the execution of the antecedent task starts ?
is there any issue in providing an async delegate to the invokation of ContinueWith ? Is the execution of the async delegate fully completed as expected ?
What is the best approach ? Passing CancellationToken.None to the invokation of Task.Run, or relying on the continuation by using ContinueWith ?
IMPORTANT NOTE: I know that using Task.Run is not the best approach in a server application (more on that can be found here), so there are probably much better ways of designing my overall architecture. I posted this question to better understanding the actual behavior of ContinueWith, because I'm not really familiar with its usage (in modern .NET code it is largely replaced by the usage of async await).
You could consider using the await using statement, that handles the asynchronous disposal of the resource automatically:
public async void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
await using var _ = resource;
try
{
await Task.Run(async () =>
{
await Task.Delay(1500, token);
}, token);
} catch (OperationCanceledException) { }
}
I also converted your fire-and-forget task to an async void (aka fire-and-crash) method. In case the unthinkable happens and your code has a bug, instead of the app continue running with an unobserved exception having occurred, resulting possibly to corrupted application state, the whole app will crash, forcing you to fix the bug ASAP.
But honestly creating a disposable resource in one method and disposing it in another is a smelly design. Ideally the method that created the resource should be responsible for disposing it finally.
I think Theodor has a great answer; I'm just going to answer some of your other questions:
do I have the guarantee that the continuation is always executed, even if the cancellation token is canceled before the execution of the antecedent task starts ?
ContinueWith will execute its delegate even of the antecedent task is already completed. In this specific case, there is no "guarantee" simply because of the nature of fire-and-forget.
is there any issue in providing an async delegate to the invokation of ContinueWith ?
ContinueWith is not async-aware, so the return type of ContinueWith is surprising for most developers. Since your code discards the return type, that's not a concern here.
Is the execution of the async delegate fully completed as expected ?
In this case, most likely, but it really depends on what "expected" means. Like all other fire-and-forget code, you can't guarantee completion. ContinueWith has an additional wrinkle: it executes its delegate using a TaskScheduler, and the default TaskScheduler is not TaskScheduler.Default but is actually TaskScheduler.Current. So I always recommend passing an explicit TaskScheduler for clarity if you really need to use ContinueWith.
What is the best approach ? Passing CancellationToken.None to the invokation of Task.Run, or relying on the continuation by using ContinueWith ?
Just drop the second argument to Task.Run.
I'll go further than that: Task.Run probably shouldn't even take a CancellationToken. I have yet to see a scenario where it's useful. I suspect the CancellationToken part of the API was copied from TaskFactory.StartNew (where it is rarely useful), but since Task.Run always uses TaskScheduler.Default, providing a CancellationToken is not useful in practice.
P.S. I recently wrote a short series on the proper solution for fire-and-forget on ASP.NET.
I have an async method. I do not have access to the Frame.Execute() code method.
Issue: myTask would not cancel until the Frame.Execute complete execution.
I need to immediately cancel Frame.Execute() and myTask when a cancellation requested.
private async Task myTask(CancellationToken tkn)
{
tkn.ThrowIfCancellationRequested();
var myList= await Task.Run(()=> Frame.Execute(),tkn);
//Do other things
}
The fact you receive CancellationToken as input parameter usually means that the token is controlled by outer code. So, the code snippet you've composed exposes following assumptions:
A task was created by outer code, and your first line (ThrowIfCancellationRequested) is absolutely legit - if the token is in "canceled" state, your code would stop the task right here.
If not canceled so far, your task then runs a new task (child task) that receives the same token as input parameter, and this is absolutely legit - so once signaled the token could terminate entire subtree of both tasks.
The only wrong thing, as mentioned by #pmcilreavy comment, is the child task code should check for token state too. Otherwise, no cancellation here, sorry.
P.S. You may find this MSDN link useful: https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-cancel-a-task-and-its-children
Frame.Execute doesn't appear to be async from what I can see that you've posted. What you are doing is starting a synchronous process in a new Task. The cancellation token you pass is only relevant to the task you've created but the Frame.Execute method doesn't get passed the cancellation token and won't pay attention to it. It most likely doesn't have an overload that accepts one as it doesnt appear to be async. So once Frame.Execute begins it won't return until it finishes at this point the task you have created gets control back and then it pays attention to your cancelled Token
hi try this write the ThrowIfCancellationRequested after the call to Frame.Execute() code method and don't send the cancelation token in the taks call, instead of that, outside of the method after the call to myTask you can use the cancellationTokenSource.Cancel(), look the next example code.
using System;
using System.Threading;
using System.Threading.Tasks;
CancellationTokenSource cancellationTokenSource =
new CancellationTokenSource();
CancellationToken tkn = cancellationTokenSource.Token;
private async Task myTask(CancellationToken tkn)
{
Console.WriteLine("Hi now i'm inside the Task");
var IsOut = await Task.Run(()=> {
while(true)
{
Console.Write("*");
Thread.Sleep(500);
}
return true;
});
tkn.ThrowIfCancellationRequested();
Console.WriteLine("This code not execute");
//Do other things
}
Console.WriteLine("Before to invoke the Taks");
Task.Run(()=> myTask(tkn));
Thread.Sleep(3000);
cancellationTokenSource.Cancel();
Console.WriteLine("");
Console.WriteLine("The End ");
So, according to an answer to this post :
2) If the body of the task is also monitoring the cancellation token
and throws an OperationCanceledException containing that token (which
is what ThrowIfCancellationRequested 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).
From this I understood that by passing a token to the constructor of the task and then calling that same token's ThrowIfCancellationRequested() method, the task would in fact terminate peacefully, without me having to catch the OperationCanceledException explicitly.
However as it turns out, an exception is thrown, so I believe I may have misunderstood the mechanic.
My code:
public void AI()
{
IsBusy = true;
var token = stopGameCancellationTokenSource.Token;
var workTask = new Task(() => aiWork(token), token);
workTask.Start();
workTask.ContinueWith(task => { IsBusy = false; });
}
private void aiWork(CancellationToken token)
{
while ( true)
{
//Some computation being done here
token.ThrowIfCancellationRequested(); //Exception is thrown here, I thought it wouldn't
//More computation here, which I don't want to happen if cancellation has benn requested
}
}
This line
token.ThrowIfCancellationRequested();
explicitly throws an exception. What the link was telling you is that if the token of the task matches the token in the OperationCanceledException that has just been thrown, "the Task transitions to the Canceled state (rather than the Faulted state)."
So the bottom line is if you don't want an exception to be thrown when the task is canceled, simply omit that line!
In addition to the explanation in #JurgenCamilleri answer of why you are getting the error, what you likely intended to do was loop until cancellation was requested. You can do that by changing your code to something like this:
private void aiWork(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
//Some computation being done here
if (token.IsCancellationRequested)
break; // need to cancel
//More computation here, which I don't want to happen if cancellation has been requested
}
}
As the name of the method suggests, ThrowIfCancellationRequested will throw an exception (OperationCanceledException) if a cancelletion was requested.
If you really don't want an exception to be thrown, you can check if token.IsCancellationRequested is true and, in this case, exit your function.
However, I'd recommand sticking with token.ThrowIfCancellationRequested() unless you got good reasons not to.
When navigating to page, i'm calling in the viewmodel
public void OnNavigatedTo()
{
ThreadPool.QueueUserWorkItem(async o =>
{
collectionsAnswer = await productCategoriesDataService.RequestServerAsync();
***
if (collectionsAnswer.status == Ok)
{
var parsedList = await productCategoriesDataService.Parse(collectionsAnswer.json);
_itemsList = new List<ProductItem>(parsedList);
DispatcherHelper.CheckBeginInvokeOnUI(() =>
RaisePropertyChanged("ItemsList", _itemsList, _itemsList, true));
}
}
How to stop/resume it properly? I tried to Abort current HttpWebResponse (which is inside of RequestServerAsync() ) from OnNavigatedFrom(), but it crashed, when i'm returning to the same page again.
So, in short, the problem is:
Navigating to page starts await commands
Leaving the page (by pressing Back) should cancel current request
Visiting page should create new request, but awaits are still waiting (if to return fast enough)
Are there better ways to solve this problem? Maybe i should create new instances of viewmodels every time?
Cancellations in the TPL and in async-await code are performed through the use of CancellationTokenSource and CancellationToken. Many asynchronous methods have an overload which accepts a CancellationToken as a parameter, this is then used by the method to observe for cancellation.
Here is a post on the MSDN about how to initiate and handle the cancellation of async tasks.
When using cancellation tokens, I would advise the use of this overload of Task.Run which takes a CancellationToken parameter instead of ThreadPool.QueueUserWorkItem. The token parameter is used internally by the Task to return a cancelled task upon cancellation, failing to use this overload could result in an OperationCanceledException being thrown from Task.Run.
I have a task and I expect it to take under a second to run but if it takes longer than a few seconds I want to cancel the task.
For example:
Task t = new Task(() =>
{
while (true)
{
Thread.Sleep(500);
}
});
t.Start();
t.Wait(3000);
Notice that after 3000 milliseconds the wait expires. Was the task canceled when the timeout expired or is the task still running?
Task.Wait() waits up to specified period for task completion and returns whether the task completed in the specified amount of time (or earlier) or not. The task itself is not modified and does not rely on waiting.
Read nice series: Parallelism in .NET, Parallelism in .NET – Part 10, Cancellation in PLINQ and the Parallel class by Reed Copsey
And: .NET 4 Cancellation Framework / Parallel Programming: Task Cancellation
Check following code:
var cts = new CancellationTokenSource();
var newTask = Task.Factory.StartNew(state =>
{
var token = (CancellationToken)state;
while (!token.IsCancellationRequested)
{
}
token.ThrowIfCancellationRequested();
}, cts.Token, cts.Token);
if (!newTask.Wait(3000, cts.Token)) cts.Cancel();
If you want to cancel a Task, you should pass in a CancellationToken when you create the task. That will allow you to cancel the Task from the outside. You could tie cancellation to a timer if you want.
To create a Task with a Cancellation token see this example:
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var t = Task.Factory.StartNew(() => {
// do some work
if (token.IsCancellationRequested) {
// Clean up as needed here ....
}
token.ThrowIfCancellationRequested();
}, token);
To cancel the Task call Cancel() on the tokenSource.
The task is still running until you explicitly tell it to stop or your loop finishes (which will never happen).
You can check the return value of Wait to see this:
(from http://msdn.microsoft.com/en-us/library/dd235606.aspx)
Return Value
Type: System.Boolean
true if the Task completed execution within the allotted time; otherwise, false.
Was the task canceled when the timeout expired or is the task still running?
No and Yes.
The timeout passed to Task.Wait is for the Wait, not the task.
If your task calls any synchronous method that does any kind of I/O or other unspecified action that takes time, then there is no general way to "cancel" it.
Depending on how you try to "cancel" it, one of the following may happen:
The operation actually gets canceled and the resource it works on is in a stable state (You were lucky!)
The operation actually gets canceled and the resource it works on is in an inconsistent state (potentially causing all sorts of problems later)
The operation continues and potentially interferes with whatever your other code is doing (potentially causing all sorts of problems later)
The operation fails or causes your process to crash.
You don't know what happens, because it is undocumented
There are valid scenarios where you can and probably should cancel a task using one of the generic methods described in the other answers. But if you are here because you want to interrupt a specific synchronous method, better see the documentation of that method to find out if there is a way to interrupt it, if it has a "timeout" parameter, or if there is an interruptible variation of it.