Cancelling an Async Task When TextBox Text Changed Then Restart It - c#

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.

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

How to add tasks to C# Task and wait for all tasks to be completed? [duplicate]

This question already has answers here:
Running multiple async tasks and waiting for them all to complete
(10 answers)
Closed 1 year ago.
I just started to touch the thread task of C#. I hope that whenever I click the button, I add a task, and then wait for all tasks to be completed before proceeding to the next step.
My code is as follows:
List<Task> SingleTaskList = new List<Task>();
private void Add_Click(object sender, RoutedEventArgs e)
{
Task t = Task.Factory.StartNew(() => { Dosomething();});
SingleTaskList.Add(Task.Factory.StartNew(() => { Dosomething(); }));
Task.Factory.ContinueWhenAll(SingleTaskList.ToArray(), tArray =>
{
Dispatcher.Invoke(() =>
{
//Update the UI
});
});
}
But I found that when any one of the tasks is completed, the next step will be taken.
How should I modify this code?
You can use Task.WhenAll like this:
await Task.WhenAll(SingleTaskList.ToArray());
Dispatcher.Invoke(() =>
{
//Update the UI
});
FYI: In order to use await there, your method will need to be marked as async or you will need to dispatch a new async task with the above statements.
There's quite a few issues with your code, and since it looks fundamentally broken I'll just answer your actual question: how to wait for multiple tasks and do something on the UI thread afterwards.
You want to use await and Task.WhenAll(), and so you need to mark your function as async. You want something like this:
private async void Add_Click(object sender, RoutedEventArgs e)
{
// fill SingleTaskList with whatever
await Task.WhenAll(SingleTaskList);
// update your UI here
// you're back on the GUI thread so no need for Dispatcher.Invoke
}

winforms: updating progress with a parallel.foreach

I haven't seen any posts pertaining to my issue, so I apologize if I post a question already asked.
I have a windows form program, c#, that checks stocks and does analysis. The main form launches another form, via a new thread and ShowDialog. While it's loading, it's running a parallel.foreach. In that parallel.foreach, I'd like to show progress on the main form.
I've run into cross-threading issues, and added invoke, although it doesn't appear to be thread-safe as it seems to be deadlocking toward the end of the parallel.foreach. I've tried delegates, events, no luck. Help me Obi-Wans, you're my only hope!
Stripped down version:
Main form
private void button1_Click(object sender, EventArgs e)
{
YearLows yearLows = new YearLows();
Thread yearLowsThread = new Thread(() => StartYearLows(yearLows));
yearLowsThread.Start();
btnGetYearLows.Enabled = false;
}
private void StartYearLows(YearLows yearLows)
{
yearLows.ShowDialog();
}
public void UpdateProgress(string text)
{
lblProgress.Text = text;
}
2nd form dialog
public partial class YearLows : Form
{
private void YearLows_Load(object sender, EventArgs e)
{
// work
Parallel.ForEach(responseStocks, new ParallelOptions { MaxDegreeOfParallelism = MaxThreads }, x =>
{
// more work
Interlocked.Increment(stocksProcessed);
UpdateProgress($"{stocksProcessed} / {stocksTotal} Researched");
});
}
private void UpdateProgress(string text)
{
Invoke(new Action(() => frmMain.UpdateProgress(text)));
}
}
Update 1:
If I move the progress update label to the child form, it appears I am getting all the progress updates. I had to move from the Load event to the Shown event so that the form renders, so users can see the progress updates. I had to follow SLaks advice though and run Task.Run(() => Parallel.ForEach. This will work for me. Would still like to figure out why it still locks up toward the end if I wanted the progress updates on the main form. (I've always read async void was bad, but I guess no way around this in these defined method signatures in winforms)
public partial class YearLows : Form
{
private async void YearLows_Shown(object sender, EventArgs e)
{
await AnalyzeStocks();
}
private async Task AnalyzeStocks(object sender, EventArgs e)
{
// work
await Task.Run(() => Parallel.ForEach(responseStocks, new ParallelOptions { MaxDegreeOfParallelism = MaxThreads }, x =>
{
// more work
Interlocked.Increment(stocksProcessed);
UpdateProgress($"{stocksProcessed} / {stocksTotal} Researched");
}));
}
private void UpdateProgress(string text)
{
Invoke(new Action(() => lblProgress.UpdateProgress(text)));
}
}
Parallel.ForEach is a blocking call; it runs delegates on the calling thread too. Therefore, the UI cannot update until it finishes.
Instead, you should use await with Task.WhenAll (if you're doing async work) or Task.Run(() => Parallel.ForEach(...)) (if it's CPU-bound) so that you leave the UI thread idle and able to update.
you can use Async Await function for this puprose... this link can be more useful to you...
PictureBox animation freezing while Task running
As per SLaks answer, an example of using Task.Run, with UI update
var tasks = new List<Task>();
foreach (var result in results)
{
tasks.Add(Task.Run(async () => {
// DO WORK
Dispatcher.Invoke(() =>
{
// UPDATE THE UI, I.E. ProgressBar.Value++;
});
}));
}
await Task.WhenAll(tasks);

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

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

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.

Categories

Resources