nested Parallel.For Loop - Cancellation.Token doesn't work - c#

I have the following problem: I have a tight loop (on purpose) which starts on a click event in the MainWindow.cs of my WPF application.
Now, a stop event triggered by another button should stop the tight loop and end the Task.
In my tight loop I have a Parallel.For loop.The idea is that I have to do a certain amount of things simultaneously (Parallel.For) and this over and over again (tight loop). I don't know if this is the best approach but it's the only one that I had, however it works :) .
I have a problem with the Cancellation.Token which doesn’t seem to do anything.
How do I stop the loop and end the Task correctly.
Here’s my code:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
CancellationToken token = cts.Token;
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Task dlTask = Task.Factory.StartNew(
() =>
{
do
{
Parallel.For(0, num, po, i => {
if (!token.IsCancellationRequested)
{
// do work
}
});
}
while (!token.IsCancellationRequested);
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
dlTask.ContinueWith(prev =>
{
//clean up
}, uiScheduler);
I tried it with po.CancellationToken.IsCancellationRequested and without and it didn't stop.
private void btnStop_Click(object sender, RoutedEventArgs e)
{
if (cts.IsCancellationRequested || po.CancellationToken.IsCancellationRequested)
{
cts.Cancel();
}
}
UPDATE: Solution thanks to Damien_The_Unbeliever:
private void btnStop_Click(object sender, RoutedEventArgs e)
{
cts.Cancel();
}

You need to call cts.Cancel() in the event handler for your stop button. This will tell your cancelation token that you have requested cancellation.

Related

How to get out a while loop when in Task.Delay

I am trying to stop a while loop in my program when an abort key is pressed, and the function running is running a Task.Delay. Unfortunately, even though this must be easy to do I just cannot get it to work for me. Please help.
I have a button that asks the user to confirm they want to run and if yes it comes to the function below and starts to run the RunSequence function. I did have this on a new thread but have now changed it to a Task, I leave the commented out code in just in case I need to run it instead of a task. RunSequence has two parameters the second is what I think I should have and that is a CancellationToken.
CancellationTokenSource tokenSource = new CancellationTokenSource();
private void ConfirmRunSequence()
{
//put it on a thread as the UI is slow to update
//var thread = new Thread(() => { RunSequence(_filePathName, tokenSource.Token); });
//thread.IsBackground = true;
//thread.Start();
Task.Run(() => RunSequence(_filePathName, tokenSource.Token), tokenSource.Token);
}
When the abort button is pressed, we set the Token to cancel and I want to drop out the While loop.
private void onAbort()
{
Abort = true; //set to abort sequence
tokenSource.Cancel();
}
I hopefully have the bits above correct, and I think the next bit is what I do not understand. Here I have a CancellationToken called _ct which I believe is tokenSource. My delay here is big so when I see the label update a few times I will then click to abort and it will be inside the delay which I want to cancel. Now this is what I cannot get to work.
I get a red sqiggly under _ct and it says “Cannot convert from System.Threading.CancellationToken to System.Threading.Task.Task”. Ok I read the words but sorry I do not know how to fix it but I also do not know if I did fix it if this is the correct way to get out the While loop, please help.
private async void RunSequence(string filePath, CancellationToken _ct)
{
Int count = 0;
while (!sr.EndOfStream)
{
lbl_count = count++;
await Task.WhenAny(Task.Delay(10000), _ct);
}
lbl_count =”aborted”;
}
Amongst the things I have tried is to change from await Task.WhenAny(Task.Delay(10000), _ct); to
Just Task.Delay(10000, _ct) but also no good.
Rather than using Task.Delay, you can access the WaitHandle of the CancellationToken and call WaitOne on that, passing a timeout.
The return value from the WaitOne operation will be true if the token was cancelled and false if the timeout was reached, from which you can then take appropriate further action.
I made a small app to show how I got my app to work. Create a small C# Winform .Net with two buttons one to run and one to abort and a label. As there was a request for the code I have included the full example program at Github
https://github.com/zizwiz/Cancellation_Token_Example
I also add a copy of some of the code below:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Cancel_Token_Example
{
public partial class Form1 : Form
{
CancellationTokenSource tokenSource; // Declare the cancellation token
public Form1()
{
InitializeComponent();
}
private void btn_run_Click(object sender, EventArgs e)
{
tokenSource = new CancellationTokenSource(); //Make a new instance
Task.Run(() => RunSequence(tokenSource.Token)); //Run the task that we need to stop
}
private void btn_abort_Click(object sender, EventArgs e)
{
tokenSource.Cancel(); // make the token a cancel token
}
private async void RunSequence(CancellationToken _ct)
{
int counter = 0;
while (!_ct.IsCancellationRequested)
{
// show incrementing number but as we have a task watch for cross threading
WriteUIData((counter++).ToString());
try
{
await Task.Delay(1000, _ct); //waits 1 second
}
catch
{
// Do nothing just needed so we can exit without exceptions
}
}
if (_ct.IsCancellationRequested)
{
//report we have cancelled
WriteUIData("Cancelled");
}
tokenSource.Dispose(); //dispose of the token so we can reuse
}
private void WriteUIData(String data)
{
// Write data to UI but as we have a task watch for cross threading
if (lbl_output.InvokeRequired)
{
lbl_output.BeginInvoke((MethodInvoker)delegate () { lbl_output.Text = data; });
}
else
{
lbl_output.Text = data;
}
}
}
}

Cancelling an Async Task When TextBox Text Changed Then Restart It

I've .NET app and there are DataGridView and TextBox on my GUI. What I want to do is when user change TextBox text, update DataGridView where cells contains this text. But this search should run as async task because if it's not, it causes freeze on GUI. Everytime when user changed TextBox text, my app should cancel if another search task running and rerun it again to search according to new search values. Here is my code;
CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)
{
cts.Cancel();
CancellationToken ct = cts.Token;
try
{
await Task.Run(() =>
{
System.Diagnostics.Debug.WriteLine("Task started");
// Searching here.
System.Diagnostics.Debug.WriteLine("Task finished");
}, cts.Token);
}
catch
{
System.Diagnostics.Debug.WriteLine("Cancelled");
}
}
On my code, tasks are canceled without it's started. I only see "Cancelled" line on debug console. I should cancel tasks because if I don't their numbers and app's CPU usage increases. Is there way to do that ?
Like Rand Random said, i should decleare new CancellationTokenSource object. I have edited my code like this and it's worked. Code should be like that:
CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)
{
cts.Cancel();
cts.Dispose();
cts = new CancellationTokenSource();
try
{
await Task.Run(() =>
{
System.Diagnostics.Debug.WriteLine("Task started");
// Searching here.
System.Diagnostics.Debug.WriteLine("Task finished");
}, cts.Token);
}
catch
{
System.Diagnostics.Debug.WriteLine("Cancelled");
}
}
If you want to make life easy for yourself, you should consider using Microsoft's Reactive Framework (aka Rx)
I'm assuming you can write this method:
async Task<string[]> DoSearchAsync(string text, CancellationToken ct)
{
/* you implement this method */
}
Then you can do this:
private IDisposable _searchSubscription = null;
private void Form1_Load(object sender, EventArgs e)
{
_searchSubscription =
Observable
.FromEventPattern(h => TextBox1.TextChanged += h, h => TextBox1.TextChanged -= h)
.Throttle(TimeSpan.FromMilliseconds(400.0))
.Select(ep => TextBox1.Text)
.Select(text => Observable.FromAsync(ct => DoSearchAsync(text, ct)))
.Switch()
.ObserveOn(TextBox1)
.Subscribe(results => { /* update your UI */ });
}
Now this watches your TextChanged event, waits 400.0 milliseconds in case you type another character - there's no sense firing off a load of searches if the user is still typing - and then it calls your new DoSearchAsync method.
Then it does a Switch which effectively means cancel any in-flight searchs and start this new one.
Finally it marshalls back to the UI thread and then gives you the results of the search so that you can update the UI.
It just handles all of the calling, background thread calls, marshalling back to the UI, all for you.
If you want to shut it down, just call _searchSubscription.Dispose().
NuGet System.Reactive.Windows.Forms and add using System.Reactive.Linq; to get the bits.

How to stop multiple long recursive async tasks on WinForms C#

I am trying to stop multiple recursive async tasks fired by an initial Parallel.Foreach loop by using CancellationToken in my WinForms C# program, the program works nice without lagging the GUI but once I click the stop button the program becomes really laggish.
With small number of tasks it works okay-ish but when the number of concurrent tasks are high in number (more than 20 let's say) it doesn't stop some tasks properly or doesn't close some of them at all.
When this bug happens and I click the start button (button1) again where there is the main Parallel.Foreach loop, for some reason it creates a smaller number of tasks.
This way I am forced to close and reopen the program.
Is there a better way to stop multiple recursive async tasks?
This is my code and what I have tried so far:
private async void button1_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
await Task.Run(() => Parallel.ForEach(Array, async s =>
{
try
{
if (!cts.Token.IsCancellationRequested)
{
await LaunchMethod(cts.Token);
}
}
catch (System.OperationCanceledException)
{
Console.WriteLine("Aborting task");
}
}));
}
And this is the recursive method:
private async Task LaunchMethod(CancellationToken ct)
{
//Really long CPU and NETWORK intensive method here
//Throwing Cancellation Request periodically during the long method like this
ct.ThrowIfCancellationRequested();
//Then calling the recursive method and passing the token
try
{
if (counter < NumberOfLoops)
{
counter++;
await LaunchMethod(ct)
}
}
catch (System.OperationCanceledException)
{
Console.WriteLine("Aborting task");
}
}
And this is the stop button
private void CancelRequest()
{
if (cts != null)
{
cts.Cancel();
cts.Dispose();
}
}

ObjectDisposedException when form is being closed

I have a timer on WinForm which I start when the form loads:
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (true)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
The problem is when I start the app in Debug mode in VS and the close it. I get an ObjectDisposedException which states that my form is already disposed.
I tried to fix it the following way:
private bool _runningTimer = true;
public MainForm()
{
InitializeComponent();
// ...
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
_runningTimer = false;
}
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (_runningTimer)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
But the problem still ocurrs. What Am I doing wrong here?
UPDATE: I know that there is a standart timer for WinForms that works great in multithreaded invironment. I just wanted to know how it is possible to make it work to better understand how to deal with race conditions. This kind of timer is just an example, it could be another process that needs to update GUI.
UPDATE 2: Following the Hans Passant and Inigmativity answers I came to that code:
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
Task task = new Task(async () => {
while (!IsDisposed)
{
Invoke(action);
await Task.Delay(1000);
}
});
task.Start();
}
But anyway if I make time interval, for example 100ms, the ObjectDisposedException still throws.
This is not real life example, I just experimenting with it...
In your first example the Task has no idea your app is exiting and is at risk of invoking the action after the label is destroyed, hence the ObjectDisposedException.
Though you attempt to alert the task in the second example, it isn't really that thread-safe and you could still invoke the action after the control is disposed.
Timers
A better solution is to just use a WinForms Timer. If you place the timer on the form via the designer, it automatically registers it as a component dependency making lifetime management much easier.
With WinForm timers you don't need to worry about threads or Tasks and more importantly you won't need to worry about Invoke if you need to update the UI (as opposed to child threads or non-UI context tasks)
Tell me more
How to: Run Procedures at Set Intervals with the Windows Forms Timer Component
Ok, I tried to use task cancellation the following way:
public MainForm()
{
InitializeComponent();
cts = new CancellationTokenSource();
Load += MainForm_Load;
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Cancel();
}
private void MainForm_Load(object sender, EventArgs e)
{
CancellationToken ct = cts.Token;
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
var task = Task.Factory.StartNew(async () => {
ct.ThrowIfCancellationRequested();
while (true)
{
Invoke(action);
await Task.Delay(100);
}
}, ct);
}
Don't know whether it's right but it seems works even if the time interval set to 10 ms.

Cancel tasks that have been initialized and run in another event handler method

I am building a C# / Windows Forms application.
Inside the click event handlers for various buttons on a form, I initialize and fire off different tasks. However, for certain button clicks, I want to cancel out any tasks that are still running that were started by certain other click event handlers.
Below is my code. The second version is my attempt so far at getting the second method to cancel out a running task started by the first method, however it does not work yet. How can I cancel the running Task?
Example Code (no cancellationtokens added yet):
private void btnFrontDoorCycle_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
// Do function 1
// Do function 2
// etc
});
}
private void btnFrontDoorClose_Click(object sender, EventArgs e)
{
// If task started in btnFrontDoorCycle_Click is running, cancel it here
Task.Factory.StartNew(() =>
{
// Do function 5
// Do function 6
// etc
});
}
Example Code (my non-functioning attempt at adding in cancellationtokens):
private CancellationTokenSource cancellationTokenSource;
private void btnFrontDoorCycle_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
// Do function 1
// Do function 2
// etc
}, cancellationToken);
}
private void btnFrontDoorClose_Click(object sender, EventArgs e)
{
// If task started in btnFrontDoorCycle_Click is running, cancel it here
if (this.cancellationTokenSource != null)
{
this.cancellationTokenSource.Cancel();
}
this.cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = this.cancellationTokenSource.Token;
Task.Factory.StartNew(() =>
{
// Do function 5
// Do function 6
// etc
});
}
You need to check if the token has been cancelled. Below is a quick bit of code I wrote just to check how it works. Hopefully you'll be able to extract what you need from it...
internal class TaskCancellationProblem
{
private CancellationTokenSource tokenSource;
private CancellationToken token;
public TaskCancellationProblem()
{
ResetSourceAndToken();
}
private void ResetSourceAndToken()
{
tokenSource = new CancellationTokenSource();
token = tokenSource.Token;
}
public void RunFirstTask()
{
// check if cancellation has been requested previously and reset as required
if (tokenSource.IsCancellationRequested)
ResetSourceAndToken();
Task.Factory.StartNew(() =>
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("Doing first task");
Thread.Sleep(1000);
}
}, token);
}
public void CancelFirstAndRunSecond()
{
// Cancel the task that was running
tokenSource.Cancel();
Task.Factory.StartNew(() =>
{
while (true)
{
Console.WriteLine("Doing second task");
Thread.Sleep(1000);
}
});
}
}
Are you checking cancellationToken.IsCancelationRequested inside your code?
I think that is the problem.
It is should be something like that:
// Do function 1
if (token.IsCancellationRequested)
{
return;
}
// Do function 2
// Were we already canceled?
ct.ThrowIfCancellationRequested();// another variant
// etc
More details: https://msdn.microsoft.com/en-us//library/dd997396(v=vs.110).aspx

Categories

Resources