How to stop multiple long recursive async tasks on WinForms C# - 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();
}
}

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;
}
}
}
}

Await task not working when triggered by code but if by user, it works

I have application for controlling LED strip. UI has combobox with effect selection and when user selects mode, it waits for currently running effect loop to finish by calling StopTask() and then executes selected effect. It sends LED color etc. to Arduino via serial. This works.
Problem is when I trigger StopTask() by MainWindow_OnClosing (when user exits the application), it triggers StopTask() but gets stuck on await currentEffectMode. I will try to explain it more by comments inside the code
MainWindow mode selection:
private void CbMode_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Checkbox selection triggers this
_ledStrip.LightModes.ChangeMode(CbMode.SelectedIndex);
}
private void MainWindow_OnClosing(object sender, CancelEventArgs e)
{
// Trigger disconnect and wait for success - this doesn't work (explained below in code comments)
_ledStrip.LightModes.Disconnect().Wait();
}
Light modes class:
private Task _modeTask;
private CancellationTokenSource _cancellationToken = new CancellationTokenSource();
// This is being triggered by change mode
internal async void ChangeMode(int mode)
{
// It waits for current loop to finish
await StopTask();
switch (mode)
{
case (int)Modes.Static:
// Then assigns new one
_modeTask = Static(_cancellationToken.Token);
break;
case (int)Modes.Breath:
_modeTask = Breath(_cancellationToken.Token);
break;
}
}
internal async Task StopTask()
{
if (_modeTask == null)
return;
// Set cancellation token to cancel
_cancellationToken.Cancel();
try
{
// and wait for task to finish. This works if triggered by user interaction BUT this is where it gets stuck when called by Disconnect() method (below). It awaits here forever
await _modeTask;
}
catch (TaskCanceledException ex)
{
Console.WriteLine(ex.Message);
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
finally
{
// After sucessful await create new cts
_cancellationToken.Dispose();
_cancellationToken = new CancellationTokenSource();
}
}
// Example of LED effect loop
internal async Task Static(CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
_ledStrip.FillLedsWithColor();
// Wait for strip to light up
await LightLeds();
// Delay before next loop round
await Task.Delay(15, cancellationToken);
}
}
// This is being called by window onclosing
internal async Task Disconnect()
{
//Stop current task and close serial connection. _device is serial
await StopTask();
Application.Current.Dispatcher.Invoke(() =>
{
if (_device.IsOpen())
{
_device.Clear();
_device.Close();
}
});
}
// Method for sending LED information to Arduino
internal async Task LightLeds()
{
if (!_device.IsOpen())
return;
await Task.Run(() =>
{
for (int i = 0; i < StaticValues.NumLeds; i++)
{
_device.Send((byte)i, _ledStrip.Leds[i].LedColor.R, _ledStrip.Leds[i].LedColor.G, _ledStrip.Leds[i].LedColor.B);
}
_device.LightUp();
});
}
I am beginner with Tasks and I am pretty sure that I am not using them properly (some of them are certainly unnecessary but I don't know it) and maybe it's the reason why it's not working. I tried to search and found many examples for using Tasks but I still don't understand it well.
Thank you!
Change MainWindow_OnClosing() to be async void instead, and use await Disconnect() instead of calling .Wait(). Event handlers are almost the only async methods for which this acceptable; the rest should have an async Task[<T>] signature instead. (There are some exceptions to the async part, but not the Task part, but I won’t muddy the waters with that here). This will stop you blocking (see link in Dmytro’s comment for more).
While there, change CbMode_OnSelectionChanged() to be similar (async void), make ChangeMode() async Task, and await it instead.
The only other minor thing of note is that if you move the device-closing code into your event handler instead (or refactor it into another method that you call from the event handler, after await Disconnect()), you should not need the Invoke(), as async event handlers - done correctly - give you this for free; i.e. effectively remaining on the UI thread while not blocking. (I’m assuming that’s what you trying to achieve there?)

Background Worker won't receive CancellationPending in c# WinForm application

I have a problem with backgroundworkers in my WinForm application.
Here is my scenario:
I have a background workers that starts in OnLoad form event. Then I have a checkbox on the form to stop/start the workers.
When I uncheck the box the event call the cancelAsync() method, but the worker don't receive CancellationPending.
To debug this problem I have tried to add a Button on the form that perform the same as the CheckedChanged event, in this case it works???!!!
This is a snippet of my code:
The workers ...
private void BwMB_DoWork(object sender, DoWorkEventArgs e)
{
bwMBExitEvent.Reset();
bool loop = true;
while (loop)
{
if (bwMB.CancellationPending)
{
loop = false;
}
... other code ...
}
e.Cancel = true;
bwMBExitEvent.Set();
}
The CheckedChanged event ...
private void checkBoxModBus_CheckedChanged(object sender, EventArgs e)
{
try
{
if (checkBoxModBus.Checked)
{
if (!bwMB.IsBusy)
bwMB.RunWorkerAsync();
}
else
{
if (bwMB.IsBusy)
{
bwMB.CancelAsync();
bwMBExitEvent.WaitOne();
}
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
And the button click event for debug ...
private void button2_Click(object sender, EventArgs e)
{
bwMB.CancelAsync();
bwMBExitEvent.WaitOne();
}
When I click on the button the worker receives the cancellation signals and exit loop setting the bwMBExitEvent (ManualResetEvent). In this way the click event WaitOne end waiting.
When I uncheck the box the worker, stop running, but don't receive the signal, so don't ends the loop and the event is not set. The CheckedChanged's WaitOne never ends.
Please excuse any English grammar or spelling issues.
First of all, BGW is obsolete, fully replaced by async/await, Tasks and Progress<T>. Tasks allow composition, continuations and cancellation, something that's quite complex with BGWs. I suspect the bwMBExitEvent event is used to implement a continuation after a BGW finishes.
The article Async in 4.5: Enabling Progress and Cancellation in Async APIs explains how cancellation and progress reporting work in .NET 4.5 and later (ie all supported versions).
That said, BGW has no problem with cancellation. I suspet the event,loop variables and other unseedn code end up causing race conditions.
Using 2, 4 or 10 cancellable tasks instead of BGW's though is easy.
Multiple tasks can be started easily with Task.Run.
It's possible to await multiple tasks to finish without blocking with Task.WhenAll.
Cancellation can be signaled to threads, tasks on asynchronous operations with CancellationTokenSource
Starting multiple tasks is easy :
private void StartTasks()
{
_cts=new CancellationTokenSource();
//Start each method passing a CancellationToken
_tasks=new[]{
Task.Run(()=>WorkerMethod1(_cts.Token)),
Task.Run(()=>WorkerMethod2(_cts.Token)),
...
};
//Enable the Cancel button
Cancel.Enabled=true;
}
This code create N tasks and stores them in an array. It also creates a new CancellationTokenSource that can be used for signalling cancellation to all tasks or threads monitorint its tokens
To cancel the tasks with a button call CancellationTokenSource.Cancel() and await for all tasks to complete :
private async void Cancel_Clicked(object sender,EventArgs args)
{
if (_cts!=null)
{
lblStatus.Text = "Cancelling";
//Signal a cancellation
_cts.Cancel();
//Asynchronously wait for all tasks to finish
await Task.WhenAll(_tasks);
_cts=null;
lblStatus.Text = "Cancelled";
}
//Disable the button
Cancel.Enabled=false;
}
By using async/await the handler isn't blocking while waiting for the tasks to finish. It doesn't need Invoke or BeginInvoke either, as execution resumes on the UI thread after await.
All the worker methods have to do is check the CancellationToken.IsCancellationRequested flag :
private void WorkerMethod1(CancellationToken token)
{
//If cancellation isn't requested
while(!token.IsCancellationRequested)
{
//Loop one more time
}
}
Putting everything together :
//Hold active tasks
Task[] _tasks;
private void WorkerMethod1(CancellationToken token)
{
//If cancellation isn't requested
while(!token.IsCancellationRequested)
{
//Loop one more time
}
}
CancellationTokenSource _cts;
private void OnLoad(...)
{
//Fire the tasks
StartTasks();
}
private void StartTasks()
{
_cts=new CancellationTokenSource();
//Start each method passing a CancellationToken
_tasks=new[]{
Task.Run(()=>WorkerMethod1(_cts.Token)),
Task.Run(()=>WorkerMethod2(_cts.Token)),
...
};
//Enable the Cancel button
Cancel.Enabled=true;
}
private async void Cancel_Clicked(object sender,EventArgs args)
{
if (_cts!=null)
{
//Signal a cancellation
_cts.Cancel();
//Asynchronously wait for all tasks to finish
await Task.WhenAll(_tasks);
_cts=null;
}
//Disable the button
Cancel.Enabled=false;
}

C# Error creating window handle on Environment.Exit call

I've created a simple thread controller class managing the thread's execution, here its code:
public class ThreadController {
int waitCount;
Thread myThread;
public ThreadController() {
//
}
public void StartThread() {
waitCount = 0;
// launch a thread to show an alert when conditions are met!
myThread = new Thread(new ThreadStart(ThreadAction));
myThread.IsBackground = true;
myThread.Start();
}
// method is async as it call an async method itself!
void ThreadAction() {
while (myThread.IsAlive) {
Thread.Sleep(5000);
bool doStop = DoStopTest().Result; // some async function testing stop criterion
if (doStop) {
MainForm.BeginInvoke(new MethodInvoker(delegate() {
MessageBox.Show("Thread stopped!");
}));
//
myThread.Abort();
}
++waitCount;
if (waitCount >= 15) {
myThread.Abort();
}
Thread.Sleep(5000);
}
}
}
Now, I want to make sure the above created threads (there might be several) are killed when I close the MainForm, which I read should be done in the FormClosing event as follows:
void Main_FormClosing(object Sender, FormClosingEventArgs e) {
// unfortunately, an error is thrown when I call following line...
Environment.Exit(Environment.ExitCode);
}
The Environment.Exit call actually generates some weird exceptions... Sometimes a "vhost32.exe stopped working", sometimes an error System.ComponentModel.Win32Exception (0x80004005): Error creating window handle or other painting events that use "Invalid Parameters"...
Am I missing something here? What is the suggested way to cleanly close the form with all associated threads, without running into errors?
The code would be a lot clearer if you used tasks and async/await. DoStopTest() seems to return a Task already, so there's no need to use a raw Thread.
The code could be something as simple as a loop :
public async Task MyTestAndWait()
{
await Task.Delay(5000);
var waitCount=0;
while( waitCount++ < 15 && !(await DoStopTest()))
{
await Task.Delay(10000);
}
MessageBox.Show("Thread stopped!");
}
After each call to await execution resumes on the original synchronization context. For desktop applications, that's the UI thread. That means there's no need to use BeginInvoke
Threads should not be aborted. The correct way is to check a thread-safe signal, like a ManualResetEvent that's raised when a thread needs to exit. When signalled, the thread's code itself should exit.
Using a lot of events can get a bit messy which is why .NET 4.5 added the CancellationToken and CancellationTokenSource classes that can be used to notify both threads and Tasks they need to cancel and exit gracefully.
public async Task MyTestAndWait(CancellationToken ct,int initialDelay,int pollDelay)
{
await Task.Delay(initialDelay,ct);
var waitCount=0;
while(!ct.IsCancellationRequested && waitCount++ < 15 && !(await DoStopTest()))
{
await Task.Delay(pollDelay,ct);
}
MessageBox.Show("Poll stopped!");
}
This will cancel the delays and the loop but it won't cancel the call to DoStepTest(). That method will have to accept a CancellationToken parameter as well
CancellationTokens are created by CancellationTokenSource classes. One of the overloads accepts a timeout, which could be used to cancel the overall operation :
public async void SendSMS_Click(object sender, EventArgs args)
{
var cts=new CancellationTokenSource(TimeSpan.FromMinutes(15));
await MyTestAndAwait(cts.Token,5000,10000);
}
The cts could be stored in a field, to allow cancellation due to another event like a button click :
CancellationTokenSource _cts;
public async void SendSMS_Click(object sender, EventArgs args)
{
SendSMS.Enabled=false;
Cancel.Enabled=true;
_cts=new CancellationTokenSource(TimeSpan.FromMinutes(15);
await MyTestAndAwait(cts.Token,5000,10000);
_cts=null;
SendSMS.Enabled=true;
Cancel.Enabled=false;
}
public async void Cancel_Click(object sender, EventArgs args)
{
_cts?.Cancel();
}
The same code can be used to signal cancellation when closing the form :
void Main_FormClosing(object Sender, FormClosingEventArgs e)
{
_cts.?Cancel();
}
BTW there's no reason to call Environment.Exit() in the form's Closing or Closed events. Closing the main form will end the application unless there's another thread running.
UPDATE
It looks like the actual question is how to verify that an SMS was sent by polling for its send status. The code in this case would be different, while still using task. The method shouldn't have any reference to the UI so it can be moved to a separate Service-layer class. After all, changing providers shouldn't result in changing UIs
Assuming HttpClient is used, it could look like this :
//In an SmsService class
public async Task<(bool ok,string msg)> SendSmsAsync(string phone,string message,CancellationToken ct)
{
var smsMsg=BuildSmsContent(phone,string);
await _httpClient.PostAsync(smsMsg,ct);
//wait before polling
await Task.Delay(_initialDelay,ct);
for(int i=0;i<15 && !ct.IsCancellationRequested;i++)
{
var checkMsg=CheckStatusContent(phone,string);
var response=await _httpClient.GetAsync(check,ct);
if (ct.IsCancellationRequested) break;
//Somehow check the response. Assume it has a flag and a Reason
var status=ParseTheResponse(response);
switch(status.Status)
{
case Status.OK:
return (ok:true,"Sent");
case Status.Error:
return (ok:failed,status.Reason);
case Status.Pending:
await Task.Delay(_pollDelay,ct);
break;
}
}
return (ok:false,"Exceeded retries or cancelled");
}
This method could be used from a button event :
CancellationTokenSource _cts;
public async void SendSMS_Click(object sender, EventArgs args)
{
DisableSending();
var phone=txtPhone.Text;
var message=txtMessage.Text;
_cts=new CancellationTokenSource(TimeSpan.FromMinutes(15);
var (ok,reason)=await _smsService.SendSmsAsync(phone,message,cts.Token);
_cts=null;
if (ok)
{
MessageBox.Show("OK");
}
else
{
MessageBox.Show($"Failed: {reason}");
}
EnableSending();
}
public void EnableSending()
{
SendSMS.Enabled=true;
Cancel.Enabled=false;
}
public void DisableSending()
{
SendSMS.Enabled=false;
Cancel.Enabled=true;
}

Assynchronous Cancelation while operation is running

I'm starting a few tests about asynchronous programing in .net and now i'm stuck at trying ti cancel a long operation using cancellationToken.
So I have the following code:
CancellationTokenSource cancelationToken = new CancellationTokenSource();
My buttons to start the operations
private void button2_Click(object sender, EventArgs e)
{
cancelationToken.Cancel(true);
}
private void button1_Click(object sender, EventArgs e)
{
StartOperation(cancelationToken.Token);
}
And finally my operations
private async void StartOperation(CancellationToken cancelToken)
{
await GetItensFromDatabase2(cancelToken);
}
public static Task<int> GetItensFromDatabase(CancellationToken cancelToken)
{
//cancelToken.Register( () => Console.WriteLine("Canceled") );
return Task.Factory.StartNew<int>(() =>
{
int result = 0;
cancelToken.ThrowIfCancellationRequested();
result = MyLongOperation(); // Simulates my operation -> I want to cancel while this operation is still running
return result;
}, cancelToken);
}
So, how to cancel MyLongOperation() method ? Is it possible to do ?
It is not possible to cancel in any point, the purpose of CancellationToken is to allow user to cancel the operation, when the long running operation expect that...
while(!finished)
{
cancelToken.ThrowIfCancellationRequested();
//Some not cancelable operations
}
Here is more common method of cancelable method
private static void LongRunning(CancellationToken cancelToken)
{
while (true)
{
if(cancelToken.IsCancellationRequested)
{
return;
}
//Not canceled, continue to work
}
}
The idea is, that user requests cancellation, but only executor decides when to stop his work. Usually executor do cancellation after reaching some "safe-point"
It is not good experiance to Abort long running operations without asking oppinion, a lot of posts have been written about this.
Well for starters you would have an issue that if you have already run through the logic which says "cancelToken.ThrowIfCancellationRequested" and then you cancel. How will the "MyLongOperation" know that you have cancelled the task? :)
Cancelling a long running task usually takes in a CancellationToken as an argument so the code would look something like:
// Check if cancelled
// do work part 1
// Check if cancelled
// do work part 2
The granularity of the cancellable operations is upto the developer.

Categories

Resources