I have this code that start a Task thread that calls a method to read data in a StreamSocket whenever it becomes available. Iy also takes a a cancellation token.
await Task.Factory.StartNew(ProcessMessage, CancelToken);
But after i call this method.
CancelToken.Cancel();
My app stills run the ProcessMessage method when i get new data in the StreamSocket. Which shouldn't be happening. It think its because i use await on it.
How can I get this task to stop when the Token is cancelled?
When you issue CancellationToken.Cancel(), the IsCancellationRequested property on every copy of the cancellation token is set to true. The objects that receive the notification can respond in whatever manner is appropriate. The typical pattern is to call token.ThrowIfCancellationRequested() within your loop somewhere.
So in your ProcessMessage routine, you need something like
while (processing)
{
// carry on your processing...
// Poll on this property if you have to do other cleanup before throwing.
if (token.IsCancellationRequested)
{
// Clean up here, then...
token.ThrowIfCancellationRequested();
}
}
You can find guidelines on implementing cancellation with await in the MSDN documentation.
Related
I developed a C# based Windows Service which runs all of its logic in several different tasks.
To allow the service to shutdown gracefully when it is being stopped, I am using a CancellationToken which is passed to any function that accepts one (mostly from 3rd party libraries which I am using) in order to abort processing before completion.
I noticed that none of those functions throw an OperationCanceledException when the cancellation is requested while the function is being called, so my application simply continues executing until I call ThrowIfCancellationRequested() somewhere else later in my code. Am I supposed to manually call ThrowIfCancellationRequested() after calling every single of those functions to make sure that the tasks stop as soon as possible, or when exactly am I supposed to call ThrowIfCancellationRequested() in my own code?
Yes, you are supposed to call ThrowIfCancellationRequested() manually, in the appropriate places in your code (where appropriate is determined by you as a programmer).
Consider the following example of a simple job processing function that reads jobs from a queue and does stuff with them. The comments illustrate the sort of thinking the developer might go through when deciding whether to check for cancellation.
Note also that you are right - the standard framework functions that accept a token will not throw a cancellation exception - they will simply return early, so you have to check for cancellation yourself.
public async Task DoWork(CancellationToken token)
{
while(true)
{
// It is safe to check the token here, as we have not started any work
token.ThrowIfCancellationRequested();
var nextJob = GetNextJob();
// We can check the token here, because we have not
// made any changes to the system.
token.ThrowIfCancellationRequested();
var jobInfo = httpClient.Get($"job/info/{nextJob.Id}", token);
// We can check the token here, because we have not
// made any changes to the system.
// Note that HttpClient won't throw an exception
// if the token is cancelled - it will just return early,
// so we must check for cancellation ourselves.
token.ThrowIfCancellationRequested();
// The following code is a critical section - we are going to start
// modifying various databases and things, so don't check for
// cancellation until we have done it all.
ModifySystem1(nextJob);
ModifySystem2(nextJob);
ModifySystem3(nextJob);
// We *could* check for cancellation here as it is safe, but since
// we have already done all the required work *and* marking a job
// as complete is very fast, there is not a lot of point.
MarkJobAsCompleted(nextJob);
}
}
Finally, you might not want to leak cancellation exceptions from your code, because they aren't "real" exceptions - they are expected to occur whenever someone stops your service.
You can catch the exception with an exception filter like so:
public async Task DoWork(CancellationToken token)
{
try
{
while(true)
{
// Do job processing
}
}
catch (OperationCanceledException e) when (e.CancellationToken == token)
{
Log.Info("Operation cancelled because service is shutting down.");
}
catch (Exception e)
{
Log.Error(e, "Ok - this is actually a real exception. Oh dear.");
}
}
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.
Here is the code I am using. What I want is for when a screen appears that code inside a while loop runs. When the screen Disappears then I need it to stop. Can someone give me advice on whether I am using the CancellationTokenSource correctly for this functionality. Also do I need to do the actions of both .Dispose() and then setting to null like I am doing here?
protected async override void OnAppearing()
{
cts1 = new CancellationTokenSource();
await GetWork(cts1.Token);
}
protected override void OnDisappearing()
{
cts1.Cancel();
base.OnDisappearing();
}
public async Task GetWork(CancellationToken ct)
{
while (true)
{
if (ct.IsCancellationRequested)
break;
// do work
}
}
The rule of thumb is:
Any code that needs to cancel the token should have access to the CancellationTokenSource
Any code that needs to respond to cancellation should have access to the CancellationToken
That's why most API code just accepts a CancellationToken - it doesn't need to actively cancel the token itself, it just needs to notice when it's been cancelled by something else.
So in your case, OnDisappearing needs access to the CancellationTokenSource, but GetWork probably only needs a CancellationToken. (It's not clear to me why you're calling Cancel within the catch block, but you probably don't need to.)
All official async .NET APIs accept a CancellationToken and also the guidelines define how it should be used. The source is just a source for the caller to handle the token.
The documentation for CancellationTokenSource also explains the use pattern:
Instantiate a CancellationTokenSource object, which manages and sends cancellation notification to the individual cancellation tokens.
Pass the token returned by the CancellationTokenSource.Token property to each task or thread that listens for cancellation.
Call the CancellationToken.IsCancellationRequested method from operations that receive the cancellation token. Provide a mechanism for each task or thread to respond to a cancellation request. Whether you choose to cancel an operation, and exactly how you do it, depends on your application logic.
Call the CancellationTokenSource.Cancel method to provide notification of cancellation. This sets the CancellationToken.IsCancellationRequested property on every copy of the cancellation token to true.
Call the Dispose method when you are finished with the CancellationTokenSource object.
I am currently developing a service which basically waits for data on a stream. The service can be cancelled at any time, but in most cases it runs for a long period (days). I like to switch my asynchronous design from using BackgroundWorker and a loop which checks the current cancellation state of the BackgroundWorker and the availability of some data on stream to a much better design using Tasks (as the BackgroundWorker is not the best choice for long running threads, as it takes a ThreadPool-Thread).
My current loop / routine looks basically like this:
while(!cancelled) {
CheckIfDataAvailable(); // This check is non-blocking
Thread.Sleep(500);
}
What I basically want to do now is get rid of the Thread.Sleep() call, but in the same time the thread should be cancelable in a fair amount of time while also listening if any data is available on a stream (and this operation might block the stream or hinders the check of the cancelation state in some other way).
I know how to cancel a Task using a CancellationToken but I am not aware of any method combining the cancel-check and the data availability check in a really clean manner (e.g. not using Thread.Sleep()).
Use Task.Delay, e.g., as follows:
async Task BackgroundWord(CancellationToken token)
{
CheckIfDataAvailable();
await Task.Delay(TimeSpan.FromSeconds(x), token);
}
If your CheckIfDataAvailable method returns a Task (that is completed when data is available), you can combine the two as follows:
await Task.WhenAny(CheckIfDataAvailable(), Task.Delay(-1, token));
Task.Delay(-1) will wait forever, so it will only transfer to the completed state if the cancellation token is cancelled. Thus Task.WhenAny will wait for either data to become available, or the token to be cancelled.
My question is about task cancellation. I have to poll the token.IsCancellationRequested to detect a cancellation. I call cts.Cancel(); in a WindowsForm Buttonmethod.
Questions:
If I hit the Button is the CancelRequest stored? Or do I have to be lucky, that to same time when I press my Button the code is at the position if (token.IsCancellationRequested)?
Is it possible to cancel my Task with for-loop by event?
Code Example:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task t1 = Task.Factory.StartNew(() =>
{
// Do syncronius work
for(int i=0; i<1000;++i)
{
DoSyncWork(i);
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancelled");
break;
}
Thread.Sleep(1000);
}
});
The cancellation request is a one time thing. Once a token source is canceled it can never be un-canceled so all derived tokens will have IsCancellationRequested always return true.
Yes it is possible, but for a for loop I don't think it is a better way. The way you use a event is you pass the callback to the CancellationToken.Register method and the callback is your event. I leave it to you how you would make a Action delegate cancel the for loop.
A few things with your code that you did not bring up:
You should never call Task.Factory.StartNew without passing in TaskScheduler, if you don't you could cause your code in the StartNew to run on the UI thread when you expect it to be on a background thread. Either use Task.Run or make sure you pass in a scheduler (TaskScheduler.Default is the one Task.Run( uses to always run on the background thread, TaskScheduler.Current is the one that is used when you don't pass anything in and is the one that can cause stuff to run on the UI thread).
If you pass the token in to the factory (or to Task.Run() then use token.ThrowIfCancellationRequested() this will cause the task to enter the Canceled state instead of the Completed state (if you forget to pass it to the factory it will enter the Faulted state), this can be useful for when you need to know when the task finished or not when you are awaiting.
The cancellation is "stored". If you call Cancel() on your CancellationTokenSource instance, the IsCancellationRequested property of the CancellationToken will be true for the rest of its existence.
As I understand it, you want to break your for loop by an event? I don't know how this should look like. The control flow of a for loop is straight forward, no event could break that. But you can use the token in the for loop's header:
for(int i=0; i<1000 && !token.IsCancellationRequested; ++i)
{
...
}
// output log if cancelled
if (token.IsCancellationRequested) Console.WriteLine(...);
if it's that what you want.
The usual implementation for cancelling out with a cancellation token is to throw the OperationCanceledException, by using the tokens .ThrowIfCancellationRequested. This allows you to catch a cancelled operation and get out of operating for however deep you are in the stack and know that it was cancelled.
For your first question, as soon as the token has been cancelled, cancellation will be requested and when you come back around in the loop the if block would be true that you have. Instead of that though I would just use token.ThrowIfCancellationRequested, and catch the specific OperationCanceledException and do any logging you want.
Second question, you can register a cancellation from anything that can access to your cancellationtokensource by calling cancel. So any event that is able to access the cancellationtokensource you could call it's cancellation event. I will often put a tokensource as an instance variable on a form that should support cancellation so that a "cancel" button or some other event that causes cancellation can call on the cancel method for the cts.
Example of one way I'll set up a form with a token:
public class MyForm
{
private CancellationTokenSource _cts;
private void Cancel()
{
if (_cts != null) {
_cts.Cancel();
}
}
}