async/await continuing too soon - c#

So I'm basically trying to delay the invocation of filter process by 1.5 seconds to allow user to type multiple keystrokes in case they want to. If a new keystroke is typed, previously waiting task is cancelled and a new one starts waiting:
System.Threading.CancellationTokenSource token = new System.Threading.CancellationTokenSource();
private async void MyTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
token.Cancel();
await System.Threading.Tasks.Task.Delay(1500, token.Token);
this.filterText = (sender as TextBox).Text;
(this.Resources["CVS"] as CollectionViewSource).View.Refresh();
//Earlier I had tried this variant too:
//System.Threading.Tasks.Task.Delay(500, token.Token).ContinueWith(_ =>
//{
// this.filterText = (sender as TextBox).Text;
// (this.Resources["CVS"] as CollectionViewSource).View.Refresh();
//});
}
But the filter process (View.Refresh() line) hits immediately on first keystroke without waiting. My impression was that calling Cancel on the token would kill Delay() and thereby the continuation task too, before planting the next one, but apparently this scheme doesn't work.
What am I missing?

The proper way to handle this is not with Task.Delay and exceptions (as exceptions are for exceptional circumstances), but using a Timer with the Timer.Elapsed event.
E.g.
using Timer = System.Windows.Forms.Timer;
private readonly Timer timer = new Timer();
private static string newText = "";
public Form1()
{
timer.Interval = 1500;
timer.Tick += OnTimedEvent;
}
private void MyTextBox_TextChanged(object sender, EventArgs e)
{
timer.Stop(); // sets the time back to 0
newText = (sender as TextBox).Text; // sets new text
timer.Start(); // restarts the timer
}
private void OnTimedEvent(Object source, EventArgs e)
{
filterText = newText;
(Resources["CVS"] as CollectionViewSource).View.Refresh();
}
(Not sure this is 100% correct, but you get the gist.)
Old snippet relating to the discussions in the comments.
As the post says: this is not needed, as Task.Delay will link a listener to the CancellationToken, thus .Cancel() will block until all listeners have heard it.
using System.Threading;
using System.Threading.Tasks;
private CancellationTokenSource cts = new CancellationTokenSource();
private Task delayTask;
private async void TenantsFilter_TextChanged(object sender, TextChangedEventArgs e)
{
cts.Cancel();
if (delayTask != null) {
try{await delayTask;}
catch(TaskCanceledException){}
}
cts = new CancellationTokenSource();
try
{
delayTask = Task.Delay(1500, cts.Token);
await delayTask;
this.filterText = (sender as TextBox).Text;
(this.Resources["CVS"] as CollectionViewSource).View.Refresh();
}
catch(TaskCanceledException)
{
}
}

If this helps anyone, the following is correctly working for me. My mistake was that I incorrectly assumed that CancellationTokenSource is a signaling device and could be used multiple times. That is apparently not the case:
private System.Threading.CancellationTokenSource cts = new System.Threading.CancellationTokenSource();
private async void TenantsFilter_TextChanged(object sender, TextChangedEventArgs e)
{
cts.Cancel();
cts = new System.Threading.CancellationTokenSource();
try
{
await System.Threading.Tasks.Task.Delay(1500, cts.Token);
this.filterText = (sender as TextBox).Text;
(this.Resources["CVS"] as CollectionViewSource).View.Refresh();
}
catch(System.Threading.Tasks.TaskCanceledException ee)
{
}
}
Posting it here for my own record and just to let others check I'm still not doing anything wrong.

Related

How to stop background worker in WPF?

I am using an MVVM model in my WPF application. I have an command binding to the cancel button. I have a start button which starts a few background workers. When i click on the cancel button, i want all the background workers to stop/quit.
With my current code when i click on cancel button, the background worker does not stop and the "StartEngineeringOperation" finishes. Can anyone please help me out with what i am doing wrong here?
Current code:
For EngineeringViewModel.cs:
public class EngineeringViewModel{
public EngineeringViewModel()
{
StartEngineering= new DelegateCommand(o =>
{
worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
if (worker.IsBusy != true) worker.RunWorkerAsync();
worker.DoWork += (s, e) =>
{
StartEngineeringOperation();
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
};
},
k => true);
Cancel = new DelegateCommand(CancelEngineeringOperation);
}
private void StartEngineeringOperation()
{
startAlarmService();
startTrendQualityCheck();
}
private void CancelEngineeringOperation(object param)
{
worker.DoWork += (s, e) =>
{
if (worker.IsBusy)
{
worker.CancelAsync();
e.Cancel = true;
return;
}
};
}
}
I tried this :
but doesn't seem to work:
private void StartEngineeringOperation()
{
startAlarmService();
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
startTrendQualityCheck();
}
As you may have learned from te comments, you need to poll the state of the BackgroundWorker in your operations that you want to support cancellation. Then take measures to cancel the ongoing operation gracefully.
The example shows how to cancel a background thread on button click. The first example uses the old BackgroundWorker and the second the modern and cleaner Task library.
BackgroundWorker
private BackgroundWorker Worker { get; set; }
private void StartWorker()
{
this.Worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
this.Worker.DoWork += BackgroundWorker_DoWork;
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
DoCancellableWork();
// Stop BackgroundWorker from executing
if (worker.CancellationPending)
{
e.Cancel = true;
}
}
private void DoCancellableWork()
{
// Check for cancellation before executing the cancellable operation and allocating resources etc..
if (this.Worker.CancellationPending)
{
return;
}
// Periodically/regularly check for the cancellation flag
for (int i = 0; i <= 10000000000; i++)
{
if (this.Worker.CancellationPending)
{
// Cancel operation gracefully e.g., do some cleanup, free resources etc.
return;
}
// Do some work
}
}
// Alternatively use a command e.g., in a view model class
private void CancelBackgroundWorker_Click(object sender, EventArgs e)
{
if (this.Worker.WorkerSupportsCancellation)
{
this.Worker.CancelAsync();
}
}
Task library
The example uses Progress<T> to report progress from the background thread to the UI thread.
private CancellationTokenSource CancellationTokenSource { get; set; }
private async Task StartWorker()
{
this.CancellationTokenSource = new CancellationTokenSource();
// Prepare callback to update UI from the background thread.
// The Progress<T> instance MUST be created on the UI thread
IProgress<int> progressReporter = new Progress<int>(progress => this.ProgressBar.Value = progress);
await Task.Run(
() => DoWork(progressReporter, this.CancellationTokenSource.Token),
this.CancellationTokenSource.Token);
this.CancellationTokenSource.Dispose();
}
private void DoWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
DoCancellableWork(progressReporter, cancellationToken);
}
private void DoCancellableWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
// Check for cancellation before executing the operation and allocating resources etc..
if (cancellationToken.IsCancellationRequested)
{
return;
}
// Periodically/regularly check for the cancellation flag
for (int i = 0; i <= 10000000000; i++)
{
if (cancellationToken.IsCancellationRequested)
{
// Cancel operation gracefully e.g., do some cleanup, free resources etc.
return;
}
// Do some work
// Report progress
progressReporter.Report(20);
}
}
// Alternatively use a command e.g., in a view model class
private void CancelBackgroundThread_Click(object sender, EventArgs e)
{
this.CancellationtokenSource?.Cancel();
}
Since the OP describes the task being done as "checking services", I would assume the work done looks something like this:
while(true){
// check service
// Post result back to UI thread
Thread.Sleep(...);
}
This is not the best way to write such such a check. As in most cases where Thread.Sleep is used, a timer would be a better alternative:
var myTimer = new System.Timers.Timer(...);
myTimer .Elapsed += OnTimedEvent;
myTimer .AutoReset = true;
myTimer .Enabled = true;
...
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
// check service
// Post result back to UI thread
}
This makes the problem of stopping/starting the task being done a simple matter of changing the Enabled-flag of the timer. It is also possible to use a timer, or a synchronization context to run the event directly on the UI thread, this is probably the best solution if "checking services" only takes a few ms.

Using await Task inside backgroundWorker is changing my status to complete

I am using a loop each 7 seconds. This loop communicates with a server. Then when server is answering i am waiting user to answer about servers response. So i need to check if all this process has finished.
So i am creating a timer
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender, e) => backgroundWorker_DoWork(sender, e, frm);
var timer = new System.Timers.Timer();
timer.Enabled = true;
timer.Interval = 10000;
timer.Elapsed += (sender, e) => Timer_Elapsed(sender, e);
timer.Start();
private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (!backgroundWorker.IsBusy)
{
backgroundWorker.RunWorkerAsync();
}
}
private static async void backgroundWorker_DoWork(object sender, DoWorkEventArgs e, Main frm)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(uri);
//message to user for answering, then return it to server again
}
}
For some reason when i am using await..., 'backgroundWorker.IsBusy' flag turns to false. I mean that i can't have correct result if task has actually finished. Should i use a flag for this? or there is more efficient way?
You can't use await inside a BackgroundWorker's DoWork. I recommend replacing BackgroundWoker completely with Task.Run:
Task task = null;
var timer = new Timer();
timer.Interval = 10000;
timer.Tick += Timer_Tick;
timer.Start();
private static async void Timer_Tick(object sender, EventArgs e)
{
if (task == null)
{
task = Task.Run(() => DoWorkAsync(frm));
try { await task; }
finally { task = null; }
}
}
private static async Task DoWorkAsync(Main frm)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(uri);
//message to user for answering, then return it to server again
}
}
Side note: I changed System.Timers.Timer to System.Windows.Forms.Timer so that the task variable is always accessed from the UI thread.

C# Background Worker Append TextBox

first off I'd like to say I'm brand new to C# so I am not too aware with how the background worker is supposed to be implemented. I have a GUI program that basically pings a domain a returns the response to a textbox. I am able to get it to work normally, however, it freezes the code because it is running on the same thread which is why I am trying to implement a background worker.
Here is the basic setup
private void button1_Click(object sender, EventArgs e)
{
url = textBox1.Text;
button1.Enabled = false;
button2.Enabled = true;
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
do
{
if (bgWorker.CancellationPending)
break;
Invoke((MethodInvoker)delegate { monitor(); });
} while (true);
}
public void monitor()
{
textBox2.AppendText("Status of: " + url + "\n");
Status(url);
System.Threading.Thread.Sleep(30000);
}
private void Status(string url)
{
// This method does all the ping work and also appends the status to the Text box as it goes through , as OK or down
}
I have not worked with bgworkers before and as you can imagine it's confusing. I've looked at tons of other articles and I can't seem to get it. Sorry if the code looks crazy, I'm trying to learn.
Use Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq;) and then you can do this:
private void button1_Click(object sender, EventArgs e)
{
var url = textBox1.Text;
Observable
.Interval(TimeSpan.FromMinutes(0.5))
.SelectMany(_ => Observable.Start(() => Status(url)))
.ObserveOn(this)
.Subscribe(status => textBox2.AppendText("Status of: " + status + "\n"));
}
You then just need to change Status to have this signature: string Status(string url).
That's it. No background worker. No invoking. And Status is nicely run on a background thread.
You've got several mistakes. First,
Invoke((MethodInvoker)delegate
{
monitor();
});
will call monitor() on your UI thread. In almost all cases you should not call methods on other threads. You especially should not call methods that block or do anything that takes more than a few milliseconds on your UI thread, and that is what this does:
System.Threading.Thread.Sleep(30000);
Instead of calling a method on another thread; submit immutable data to the other thread and let the thread decide when to handle it. There is an event already built in to BackgroundWorker which does that. Before you call bgWorker.RunWorkerAsync() do this:
url = new Uri(something);
bgWorker.WorkerReportsProgress = true;
bgWorker.WorkerSupportsCancellation = true;
bgWorker.ProgressChanged += Bgw_ProgressChanged;
private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox2.AppendText("Status of: " + url + ": " + e.UserState.ToString()
+ Environment.NewLine);
}
Your bgWorker_DoWork should look more like this:
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgw.CancellationPending)
{
System.Threading.Thread.Sleep(new TimeSpan(0, 0, 30));
var status = ResultOfPing(e.Argument as Uri);
bgw.ReportProgress(0, status);
}
e.Cancel = true;
}
and you should call it like this:
bgWorker.RunWorkerAsync(url);
You've got a second problem. BackgroundWorker creates a thread, and your thread is going to spend most of its time blocked on a timer or waiting for network responses. That is a poor use of a thread. You would be better off using completion callbacks or async/await.
The background worker is running on a thread pool thread, but your call to Status and Sleep is running on the UI thread. You need to move that stuff back into bgWorker_DoWork.
Try this code:
public partial class Form1 : Form
{
bool cancel;
public Form1()
{
InitializeComponent();
}
public void StartPinging()
{
this.cancel = false;
startButton.Enabled = false;
stopButton.Enabled = true;
responseBox.Clear();
responseBox.AppendText("Starting to ping server.");
responseBox.AppendText(Environment.NewLine);
var bw = new BackgroundWorker
{
WorkerReportsProgress = false,
WorkerSupportsCancellation = true
};
bw.DoWork += (obj, ev) =>
{
while (!cancel)
{
// Ping Server Here
string response = Server.PingServer();
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText(response);
responseBox.AppendText(Environment.NewLine);
}));
}
};
bw.RunWorkerCompleted += (obj, ev) =>
{
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText("Stopped pinging the server.");
responseBox.AppendText(Environment.NewLine);
startButton.Enabled = true;
stopButton.Enabled = false;
}));
};
bw.RunWorkerAsync();
}
delegate void UiMethod();
private void startButton_Click(object sender, EventArgs e)
{
StartPinging();
}
private void stopButton_Click(object sender, EventArgs e)
{
responseBox.AppendText("Cancelation Pressed.");
responseBox.AppendText(Environment.NewLine);
cancel = true;
}
}
public class Server
{
static Random rng = new Random();
public static string PingServer()
{
int time = 1200 + rng.Next(2400);
Thread.Sleep(time);
return $"{time} ms";
}
}
Erwin, when dealing with C# - threads and UI elements usually you will come across cross-thread operations i.e. Background thread with UI threads. This interaction needs to be done in thread safe way with the help of Invoke to avoid invalid operations.
Please look into below resource: InvokeRequired section.
https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls

Background task in UWP

I want to use a background task for my UWP app.
The below code, is my back button click event in windows mobile:
private async void MainPage_BackRequested(object sender, BackRequestedEventArgs e)
{
var access= await BackgroundExecutionManager.RequestAccessAsync();
var task = new BackgroundTaskBuilder
{
Name="My task",TaskEntryPoint=typeof(backGroundTask.Class1).ToString()
};
trigger = new ApplicationTrigger();
task.SetTrigger(trigger);
task.Register();
//var result = await trigger.RequestAsync();
if (Frame.CanGoBack)
{
Frame.GoBack();
e.Handled = true;
}
}
public void Run(IBackgroundTaskInstance taskInstance)
{
_deferral = taskInstance.GetDeferral();
clearData();
count1 = 0;
getDownloadedSongs();
dispatcherTimer1.Tick += DispatcherTimer1_Tick;
dispatcherTimer1.Interval = new TimeSpan(0, 0, 3);
dispatcherTimer1.Start();
_deferral.Complete();
}
DispatcherTimer dispatcherTimer1 = new DispatcherTimer();
private async void DispatcherTimer1_Tick(object sender, object e)
{
try
{
clearData();
}
catch (Exception ex)
{
}
}
But this code is not working, when I click back button. As per expectation background task code should work, but it is not working. What am I doing wrong?
Your background task is exiting before the DispatcherTimer gets a chance to ever execute, because you mark the Deferral as complete. You should hold on to the Deferral until all the work in your background task has been completed (or until you receive a TaskCanceled event from the system).

Enabling responsive GUI while processing

I have a lengthily processing in my winform when I click a button; namely, i'm loading lots of files and processing them. For the duration of the processing, my GUI is frozen and unresponsive which is a problem since the processing can take an upward of 10 minutes. Is there a way of putting the code in some sort of bubble or something so I can use the GUI while processing the files? Maybe even add the "Cancel" button.
EDIT: René's solution works, also here's progressbar control I also wanted:
private async void button1_Click(object sender, EventArgs e)
{
progressBar1.Maximum = ValueWithTOtalNumberOfIterations.Length;
IProgress<int> progress = new Progress<int>(value => { progressBar1.Value = value;});
await Task.Run(() =>
{
var tempCount = 0;
//long processing here
//after each iteration:
if (progress != null)
{
progress.Report((tempCount));
}
tempCount++;
}
}
You could simply make your button's click handler async and start a Task for your long running operation:
public async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false; // disable button to not get called twice
await Task.Run(() =>
{
// process your files
}
button1.Enabled = true; // re-enable button
}
The compiler turns this into a state machine. The control flow is returned to the caller (the UI) at the await keyword. And execution of this method is resumed when your Task has completed.
To use a "Cancel"-Button you can use a TaskCancellationSource or simply define a flag that you check while you're processing your files, and return if the flag is set (by the click handler of your "Cancel" button):
private bool _stop = false;
private void cancelButton_Click(object sender, EventArgs e)
{
_stop = true;
}
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false; // disable button to not get called twice
_stop = false;
await Task.Run(() =>
{
// process your files
foreach(var file in files)
{
if (_stop) return;
// process file
}
}
button1.Enabled = true; // re-enable button
}

Categories

Resources