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();
}
}
}
Related
I'm really new to threading multitasking/multithreading, but I'm working on a project where I think I need it. The user will be editing a fairly complex diagram, and I want the program to check for validity of the diagram. The validity check is non-trivial (polynomial time, though, not NP - seconds, not minutes or years, but I don't want to hold the user up for a few seconds after every change) so I would like the program to check for validity in the background and highlight inconsistencies when it finds them. When the user makes certain changes to the diagram (changes the structure, not just the labels on elements), the validation will have to throw away what it was doing and start again. I'm assuming the user will eventually take a break to think/go for a pee/go for a coffee/chat to that rather cute person two cubicles along, but in case they don't, I have to let the validation run to completion in some circumstances (before a save or a printout, for example). Broad-brush, what are the features of C# I'll need to learn, and how do I structure that?
Broad Brush. Here we go.
Q: "What are the features of C# I'll need to learn?"
A: You can get by nicely with a basic toolkit consisting (roughly speaking) of:
System.Threading.Tasks.Task
System.Threading.CancellationTokenSource
System.Threading.SemaphoreSlim
Q: "I don't want to hold the user up for a few seconds after every change"
A: OK, so we will never-ever block the UI thread. Fire off a Task to run a background validation routine that checks every now and then to see if it's been cancelled.
CancellationTokenSource _cts = null;
SemaphoreSlim ssBusy = new SemaphoreSlim(2);
private void ExecValidityCheck()
{
ssBusy.Wait();
Task.Run(() =>
{
try
{
_cts = new CancellationTokenSource();
LongRunningValidation(_cts.Token);
}
finally
{
ssBusy.Release();
}
})
.GetAwaiter()
.OnCompleted(CheckForRestart);
}
We'll call CheckForRestart using GetAwaiter().OnCompleted(). This just means that without blocking we'll be notified as a callback when the thread finishes for one of three reasons:
Cancelled
Cancelled, but with an intent to start the validation over from the beginning.
Ran validation to completion
By calling CheckForRestart we determine whether to start it over again or not.
void CheckForRestart()
{
BeginInvoke((MethodInvoker)delegate
{
if (_restart)
{
_restart = false;
ExecValidityCheck();
}
else
{
buttonCancel.Enabled = false;
}
});
}
Rather that post the complete code here, I pushed a simple working example to our GitHub. You can browse it there or clone and run it. 20-second screen capture. When the RESTART button is clicked in the video, it's checking the CurrentCount property of the Semaphore. In a threadsafe way it determines whether the validation routine is already running or not.
I hope I've managed to give you a few ideas about where to start. Sure, the explanation I've given here has a few holes but feel free to address your critical concerns in the comments and I'll try to respond.
You probably need to learn about asynchronous programming with async/await, and about cooperative cancellation. The standard practice for communicating cancellation is by throwing an OperationCanceledException. Methods that are intended to be cancelable accept a CancellationToken as argument, and observe frequently the IsCancellationRequested method of the token. So here is the basic structure of a cancelable Validate method with a boolean result:
bool Validate(CancellationToken token)
{
for (int i = 0; i < 50; i++)
{
// Throw an OperationCanceledException if cancellation is requested
token.ThrowIfCancellationRequested();
Thread.Sleep(100); // Simulate some CPU-bound work
}
return true;
}
The "driver" of the CancellationToken is a class named CancellationTokenSource. In your case you'll have to create multiple instances of this class, one for every time that the diagram is changed. You must store them somewhere so that you can call later their Cancel method, so lets make two private fields inside the Form, one for the most recent CancellationTokenSource, and one for the most recent validation Task:
private Task<bool> _validateTask;
private CancellationTokenSource _validateCTS;
Finally you'll have to write the logic for the event handler of the Diagram_Changed event. It is probably not desirable to have multiple validation tasks running side by side, so it's a good idea to await for the completion of the previous task before launching a new one. It is important that awaiting a task doesn't block the UI. This introduces the complexity that multiple Diagram_Changed events, along with other unrelated events, can occur before the completion of the code inside the handler. Fortunately you can count on the single-threaded nature of the UI, and not have to worry about the thread-safety of accessing the _validateTask and _validateCTS fields by multiple asynchronous workflows. You do need to be aware though that after every await these fields may hold different values than before the await.
private async void Diagram_Changed(object sender, EventArgs e)
{
bool validationResult;
using (var cts = new CancellationTokenSource())
{
_validateCTS?.Cancel(); // Cancel the existing CancellationTokenSource
_validateCTS = cts; // Publish the new CancellationTokenSource
if (_validateTask != null)
{
// Await the completion of the previous task before spawning a new one
try { await _validateTask; }
catch { } // Ignore any exception
}
if (cts != _validateCTS) return; // Preempted (the event was fired again)
// Run the Validate method in a background thread
var task = Task.Run(() => Validate(cts.Token), cts.Token);
_validateTask = task; // Publish the new task
try
{
validationResult = await task; // Await the completion of the task
}
catch (OperationCanceledException)
{
return; // Preempted (the validation was canceled)
}
finally
{
// Cleanup before disposing the CancellationTokenSource
if (_validateTask == task) _validateTask = null;
if (_validateCTS == cts) _validateCTS = null;
}
}
// Do something here with the result of the validation
}
The Validate method should not include any UI manipulation code, because it will be running in a background thread. Any effects to the UI should occur after the completion of the method, through the returned result of the validation task.
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.
private async void TriggerWeekChanged(Week currentWeek)
{
await LoadDataForSelectedWeek(currentWeek); //Split into multiple methods
}
In case a user hammers on the Change_Week Button how can I cancel the current Task, and start a new one with the new paramerters ?
I tried like this:
private async Task Refresh(Week selectedWeek, CancellationToken token)
{
Collection.Clear();
await LoadDataFromDatabase();
token.ThrowIfCancellationRequested();
await ApplyDataToBindings();
token.ThrowIfCancellationRequested();
//Do some other stuff
}
Problem is:
In my Collection I got data from multiple weeks when I hit the button to fast in a row.
Everything that is awaited will need to have visibility on that CancellationToken. If it is a custom Task that you wrote, it should accept it as an argument, and periodically within the function, check to see if it has been cancelled. If so, it should take any actions needed (if any) to stop or rollback the operation in progress, then call ThrowIfCancellationRequested().
In your example code, you propbably want to pass token in to LoadDataFromDatabase and ApplyDataToBindings, as well as any children of those tasks.
There may be some more advanced situations where you don't want to pass the same CancellationToken in to child tasks, but you still want them to be cancellable. In those cases, you should create a new, internal to the Task, Cancellationtoken that you use for child tasks.
An important thing to remember is that ThrowIfCancellationRequested marks safe places within the Task that the Task can be stopped. There is no guaranteed safe way for the runtime to automatically detect safe places. If the Task were to automatically cancel its self as soon as cancellation was requested, it could potentially be left in an unknown state, so it is up to developers to mark those safe locations. It isn't uncommon to have several calls to check cancellation scattered throughout your Task.
I've just notice that your TriggerWeekChanged function is async void. This is usually considered an anti-pattern when it is used on something that is not an event handler. It can cause a lot of problems with tracking the completed status of the async operations within the method, and handling any exceptions that might be thrown from within it. You should be very weary of anything that is marked as async void that isn't an event handler, as it is the wrong thing to do 99%, or more, of the time. I would strongly recommend changing that to async Task, and consider passing in the CancellationToken from your other code.
You did not mention about how LoadDataForSelectedWeek and Refresh interact.
For short you need to create one CancellationTokenSource instance that handle every click. Then pass it to that method and do Cancel method every time new one appear.
private async void TriggerWeekChanged(Week currentWeek, CancellationTokenSource tokenSource)
{
tokenSource.Cancel();
try
{
var loadDataTask = Task.Run(() => LoadDataForSelectedWeek(currentWeek, tokenSource.Token), tokenSource.Token); //Split into multiple methods
}
catch(OperationCanceledException ex)
{
//Cancelled
}
}
LoadDataForSelectedWeek -> Refresh (?)
private async Task Refresh(Week selectedWeek, CancellationToken token)
{
Collection.Clear();
await LoadDataFromDatabase();
token.ThrowIfCancellationRequested();
await ApplyDataToBindings();
token.ThrowIfCancellationRequested();
//Do some other stuff
}
I have a Thread with this code:
foreach (string file in allDirectoriesFiles)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
// ... some operation...
}
// ... some operation 2...
notice that I use return instead of break, because if I stop the thread the some operation 2 must not be executed.
I also notice that in this manner the .ContinueWith it is not executed.
Well, but that is just a "return" function. How can C# know that I'm returning due to the tokenSource.Cancel() instead of a usual return?
You should not create the continuation with the same cancellation token or else the continuation will also be cancelled, even before it is executed. Create the continuation with a different cancellation token (from a different cancellation source) or simply create it without a cancellation token if you always want the cancellation to execute.
A CancellationToken is meant to propagate the cancelling to the whole set of actions at once. Once you Cancel() its source, the other continuations won't be called.
I have different tasks to read from different files and find a word into them. I have put them into a task array which I start with waitAny method as following :
foreach (string file in filesList)
{
files[i] = Task.Factory.StartNew(() =>
{
mySearch.Invoke(file);
});
i++;
}
System.Threading.Tasks.Task.WaitAny(files);
I would like to stop all other tasks as soon as one of the tasks finishes (it finishes when it founds the word). For the moment, with waitany, i can know when one tasks finishes, but I don't know how I could know which one has finished and how to stop other tasks.
What would be the best way to achieve this ?
You can use single CancellationToken which all tasks will share. Inside mySearch.Invoke method verify value of token.IsCancellationRequested to cancel task. When some task will be finished cancel others via CancellationTokenSource.Cancel().
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
foreach (string file in filesList)
{
// pass cancellation token to your task
files[i] = Task.Factory.StartNew(() => mySearch.Invoke(file, token), token);
i++;
}
Task.WaitAny(files);
tokenSource.Cancel();
BTW you can force token to throw OperationCanceledException when source is canceled by calling token.ThrowIfCancellationRequested()
When creating a Task you can pass a CancelationToken. Set this token when one of the tasks finishes.
This will cause remaining tasks with this token to not execute. Running tasks can receive a OperationCanceledException and stop too.
I highly suggest reading How do I cancel non-cancelable async operations? by Stephen Toub. Essentially what you need to do is cancel all of these tasks, but currently you have no mechanism to cancel them.
The ideal approach would be to create a CancellationTokenSource before the foreach, pass the CancellationToken from that source to each of the child tasks, check that token periodically and stop doing work when you notice it's indicated cancellation. You can then cancel the token source in the WhenAny continuation.
If that's not an option you need to decide if it's important to actually stop the tasks (which, really, just can't be done) or if you just need to continue on with your code without waiting for them to finish (that's easy enough to do).