Disable Winforms button while OnClick code executes in background - c#

I have some code in my "button_click" action. I want to disable the button during this code working.
private void button1_Click(object sender, RoutedEventArgs e)
{
button1.IsEnabled = false;
// some code (its take about 2-5 sec)
button1.IsEnabled = true;
}
But this doesn't work. The button never disables.

You need to run the "some code" part on a background thread:
button1.Enabled = false;
Task.Factory.StartNew(() => {
// some code (its take about 2-5 sec)
}).ContinueWith(task => {
button1.Enabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());

That is because your UI locks up during the entire action.
You should write the task in some sort of background thread.
You can use the BackgroundWorker for that, but better a Task.
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += bgw_DoWork;
bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
button.Enabled = false;
bgw.RunWorkerAsync();
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
// your task
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// update the button
button.Enabled = true;
}

Dispatcher is responsible for message pumping in WPF. Every thread has dispatcher associated with it which is responsible for queuing stuffs on that thread based on DispatcherPriority of items.
In your case GUI rendering is done on DispatcherPriority.Render but right now dispatcher is busy executing your button click event handler code so GUI rendering never happens until it finishes with your code execution. That's why you see button to be refreshed only when your handler gets executed completely.
On a sidenote what McGarnagle proposed gonna work but you can do it other way round as well by explicitly queuing empty delegate on dispatcher with priority Render which will force all queued items with priority higher or equal to Render to be processed before proceeding further. Hence, you will see refresh on your GUI:
button1.IsEnabled = false;
Application.Current.Dispatcher.Invoke((Action)(() => { }),
DispatcherPriority.Render);
// some code (its take about 2-5 sec)
button1.IsEnabled = true;

Related

Progress Bar with Label — Unable to Update Label in ProgressChanged

I have a background worker with a long running task. The task goes through a list of files and I want to update the user with which file we are on. I have a tool strip that has a label named panel1.text. The progress bar is working however the label is not changing in my ProgressChanged method i.e. It should say Processing File1 then change to Processing File2, but it stays on the default of Processing.
private void btnProcess_Click(object sender, EventArgs e)
{
toolStripProgressBar1.Visible = true;
toolStripProgressBar1.Maximum = 1000000000;
panel1.Text = "Processing "; // this appears properly
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(processFiles);
worker.ProgressChanged += ProgressChanged;
worker.RunWorkerAsync();
while (worker.IsBusy)
{
// the reason for this is because nothing can happen until the processing is done
toolStripProgressBar1.Increment(1);
}
// more processing
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
panel1.Text = "Processing "+ e.UserState.ToString(); <<<---- This is Not Updating panel1.Text but it evaluates properly
}
private void processFiles(object sender, EventArgs e)
{
int retVal = 0;
foreach (string fileName in listBox1.Items)
{
ProgressChangedEventArgs ea = new ProgressChangedEventArgs(1,fileName);
ProgressChanged(this, ea);
// do more processing
}
}
I would appreciate any help.
You are using the same thread, which is being blocked by another process. You need to use a Task to create a new thread and possibly use Dispatcher.BeginIvoke if the control is on the other thread. Make sure whatever Button Click, etc is happening is marked with the Async keyword as well to make it Asynchronous.
Example:
Await Task mytask = Task.Run(() =>
for(var i = 0; i < 1000; i++)
{
Label.Dispatcher.BeginInvoke( () =>
UpdateMe(int i, LabelClass/Component class/component)});
Then inside the Label Class or wherever the label is:
Public void UpdateMe(int i, LabelClass class)
{
class.label.content = Cint((i/Total)*100);
Thread.Sleep(500);
}
There are other ways to do it as well such as Binding the value to the UI, but this will give you a better understanding of why its not working and how things work with other threads.
If you want to really get a visual understanding call:
`Console.WriteLine($"Current Thread ID: System.Threading.Thread.CurrentThread.ManagedThreadId}");`
Right before you go into the Task---it will give you the main thread ID
Then inside the Task call it again...this will give you the secondary thread ID.
Then Right before the Dispatcher call:
Console.WriteLine($"Do I have access to the label on this thread? {Label.Dispatcher.CheckAccess()}";
If you have access it will display True, if not it will display False...In your case it will display false because its owned by the other thread, but you can use the Dispatcher to be able to do work on that thread while in another thread...
Also, I recommend you not use Background Worker and use Tasks instead...this explains why in depth...basically Tasks do everything Background workers do and more, have less issues and are easier to work with...
http://blog.stephencleary.com/2013/09/taskrun-vs-backgroundworker-conclusion.html
As already commented by Ivan, remove the while loop while (worker.IsBusy) as it's blocking the UI thread to process further. As well, you should enable the WorkerReportsProgress to true
worker.WorkerReportsProgress = true;
worker.ProgressChanged += ProgressChanged;
while (!worker.IsBusy)
{
worker.RunWorkerAsync();
}
Per your comment, move those later processing to BackgroundWorker.RunWorkerCompleted Event

BackgroundWorker not exiting loop

I have an BackgroundWorker:
BackgroundWorker worker;
private void Form1_Load(object sender, EventArgs e)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged +=
new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
DoWork Event
void worker_DoWork(object sender, DoWorkEventArgs e)
{
int percentFinished = (int)e.Argument;
while (!worker.CancellationPending && percentFinished < 100)
{
percentFinished++;
worker.ReportProgress(percentFinished);
//here I start my operation
//operation....
//operation end
}
e.Result = percentFinished;
}
Progresschanged
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
Completed
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Asynchroner Thread kam bis zum Wert:
"+e.Result.ToString());
btnStartEnd.Text = "Starten";
}
And finally my button:
private void btnStartEnd_Click(object sender, EventArgs e)
{
if (worker.IsBusy)
{
worker.CancelAsync();
btnStartEnd.Text = "Starten";
}
else
{
if (progressBar1.Value == progressBar1.Maximum)
{
progressBar1.Value = progressBar1.Minimum;
}
worker.RunWorkerAsync(progressBar1.Value);
btnStartEnd.Text = "Stoppen";
}
}
This code works but I get a loop for my operations until the percentage is 100, so the operation starts 100 times and so takes 100 times longer.
The goal should be that the operation only starts one time and the percentage counts from 1-100.
Maybe I understand something wrong, but how does the worker know how far the operation is done? That value should be send to the progress bar for visualisation.
Normally you won’t add the loop inside the DoWork method
If you want to load for example 100 files from the file system and report the progress it could look like that:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for(int i = 0; i < 100; i++)
{
// Load file and do something with the content
...
// Report the progress which causes the ProgressChanged event to be fired
// And update progressbar with the UI thread
worker.ReportProgress(i);
}
}
If you only have one long running operation that needs to be executed inside the DoWork method it needs to be executed asynchronously
Here is one example how you could call an action asynchronously in .NET:
Action action = () =>
{
for(int i = 0; i <100; i++)
{
Console.WriteLine(String.Format("Step {0} of long running operation", i));
Thread.Sleep(1000);
}
};
var r = action.BeginInvoke(null, null);
while(!r.IsCompleted)
{
Console.WriteLine("Waiting...");
Thread.Sleep(100);
}
However in .NET there are many more ways to do it. See for example:
http://msdn.microsoft.com/en-us/library/jj152938(v=vs.110).aspx for
Async patterns in .NET
http://msdn.microsoft.com/de-de/library/hh191443.aspx for Async
programming with await/async (new in .NET 4.5)
Maybe I understand something wrong, but how does the worker know how far the operation is done? That value should be send to the progress bar for visualisation.
The BackgroundWorker class doesn't know anything about your operations and about its progress. It's your job to determine when to report the progress.
In general the workflow for a background worker looks like this:
UI thread calls RunWorkerAsync.
DoWork event handler is called on a different thread. During the event handler you can report progress using the ReportProgress method
If you report a progress then the ProgressChanged event handler is called on the UI thread. Here you can update a progress bar for example.
When your event handler for the DoWork event exits the RunWorkerComplete event is raised.
Now why does every example for the BackgroundWorker has a for-loop? Because it's very easy to write, and measuring progress for a for-loop is trivial. Unfortunately this quite often isn't useful for different kind of operations.
If your long running operation processes N files then it's pretty obvious that you can update the progress bar after every item by 1/N. That's what the for-loop example does.
But if you only have one long running operation then you simply don't have any chance to get the progress unless the operation itself supports reporting it or if you can somehow estimate the progress.
The BackgroundWorker can't magically give a long running operation a progress bar. It only enables you to run the operation in the background.

C#- background worker's CancelAsync() not working?

I want to abort the process but not able to do so, I am using Background worker with my functions of processing.
public void Init()
{
bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (bw.CancellationPending == true)
{
e.Cancel = true;
}
else
{
e.Result = abd();
}
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
{
lbltext.content="Canceled";
}
else
{
lbltext.content="Completed";
}
}
private void btncan_Click(object sender, RoutedEventArgs e)
{
bw.CancelAsync();
}
private void btnstart_Click(object sender, RoutedEventArgs e)
{
bw.RunWorkerAsync();
}
I am not able to abort the process using this code.
Function abd() is performing the processing part and returning the result.
Please provide me any solution.
Thanks.
When you call bw.CancelAsync() you just set CancellationPending flag to true. It does not cancels something by default. You need to handle pending cancellation manually. But you can't do that with your code, because when you click button, there are three possible options:
Long-running abd() method finished it's work and there is nothing to cancel
abd() started it's work, and background worker is blocked - it's waiting for results of abd(), then it continues execution - i.e. exits if-else block and raises RunWorkerCompleted event.
Nearly impossible option - you will be fast as light, and you will click button before if-else block entered. Than CancellationPending will be true, and abd() will not start execution
If you want to use cancellation, then do your long-running task in a loop, and check if cancellation is pending on each step:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
List<Foo> results = new List<Foo>();
// any loop here - foreach, while
for(int i = 0; i < steps_count; i++)
{
// check status on each step
if (bw.CancellationPending == true)
{
e.Cancel = true;
return; // abort work, if it's cancelled
}
results.Add(abd()); // add part of results
}
e.Result = results; // return all results
}
Probably DoWork may have finished its work before calling CancelAsync and as mentioned in the docs e.Cancelled may be false..
Docs say this
Be aware that your code in the DoWork event handler may finish its
work as a cancellation request is being made, and your polling loop
may miss CancellationPending being set to true. In this case, the
Cancelled flag of System.ComponentModel.RunWorkerCompletedEventArgs in
your RunWorkerCompleted event handler will not be set to true, even
though a cancellation request was made. This situation is called a
race condition and is a common concern in multithreaded programming.
How about the following?
While(!bw.CancellationPending)
{
//do some work!
}
e.Cancel = true;

C# Threading Run and Cancel button, need to be able to cancel long proccessing run

When a user clicks on Run, the application runs through a lot of code to generate a model and display it in a Chart. The Run takes about 1-2 minutes to run. I also have a Cancel button that gets enabled after the Run button is clicked. I am working with DotSpatial, so my buttons are on a plugin panel in a ribbon UI. The click event on the Run and Cancel start in the plugin, which calls the back-end class's code Run and Click.
When the user hits cancel after the run starts, there is a delay, but the cancel method is invokes and executes, but the run never stops and we eventually see the chart display. So, I'm thinking I need a separate thread for the Run. I'm fairly new to programming, and never worked with Threading. I've looked into it and added the below code, but my thread method isn't running. Here's my code:
The Run button is clicked:
This is at the top:
//check to see if RunModel thread needs to stop or continue
private volatile bool stopRun = false;
private Thread runThread;
Then this is the method that's called from the click event:
public void btnRun_testingThread(object sender, EventArgs e)
{
//create a new thread to run the RunModel
if (runThread == null)
{
//we don't want to stop this thread
stopRun = false;
runThread = new Thread(RunModel);
runThread.Start(); <--this isn't doing anything
}
So, I would think that when the code gets to the runThread.Start(), it would jump into my RunModel method and start running through the code. But it doesn't. Additionally, I'll want to cancel out of this thread (once I have it working correctly), so I have this, which gets called from the cancel click method:
private void StopRunThread()
{
if (runThread != null)
{
//we want to stop the thread
stopRun = true;
//gracefully pause until the thread exits
runThread.Join();
runThread = null;
}
}
Then the this is the RunModel() where I'm checking occasionally to see if the stopRun bool has changed.
public void RunModel()
{
...some code.....
//check to see if cancel was clicked
if (stopRun)
{
....clean up code....
return;
}
....some more code....
//check to see if cancel was clicked
if (stopRun)
{
....clean up code....
return;
}
}
And the cancel button click method:
public void btnCancel_Click(Object sender, EventArgs e)
{
stopRun = true;
StopRunThread();
//the model run has been canceled
....some code.....
}
Any help on getting the thread.start to actually run the Run method? Then do I need to constantly check the volatile bool in the run in order to clean everything up if it's being stopped? Thanks!
I think you'd be best looking at the BackgroundWorker - this essentially runs separately but can watch out for cancellation commands. Make sure you add 'WorkerSupportCancellation' when you initialise it:
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork); // This does the job ...
backgroundWorker1.WorkerSupportsCancellation = true; // This allows cancellation.
Then on click you can start your process:
public void btnRun_testingThread(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
Your cancel button can issue a cancellation request:
public void btnCancel_Click(Object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
Then your worker can monitor for this as it's doing it's work ...
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
if (backgroundWorker1.CancellationPending)
{
break;
}
else
{
// Do whatever you're doing.
}
}
e.Result = backgroundWorker1.CancellationPending ? null : orders;
}
You can enhance this further by adding progress bars etc., but that gets a bit more complicated so I won't go into it here.
Considering new info provided in commend I believe you just missed a start of the RunModel() method in debugger because of wrong assumption regarding thread.Start() method behaviour.
Please see a note from MSDN, Thread.Start Method
Once a thread is in the ThreadState.Running state, the operating
system can schedule it for execution. The thread begins executing
at the first line of the method represented by the ThreadStart or
ParameterizedThreadStart delegate supplied to the thread constructor.
Small demonstration that thread start takes some time bits, for me it starts in 38-40 milliseconds:
Stopwatch watch = new Stopwatch();
Thread thread = new Thread((ThreadStart)watch.Stop);
thread.Start();
watch.Start();
Thread.Sleep(5000);
double startedAfter = watch.ElapsedMilliseconds;
Since .NET Framework 4.0 consider using TPL Tasks rather than threads explicitly, some pros:
You can easily synchronize with UI thread by passing in a Task UI Thread synchronization context
You can easily stop a Taks using CancellationToken

Restart background worker

Is there a way to directly "restart" a background worker?
Calling CancelAsync() followed by RunWorkerAsync() clearly won't do it as their names imply.
Background info:
I have a background worker which calculates a total in my .net 2.0 Windows Forms app.
Whenever the user modifies any value which is part of this total I'd like to restart the background worker in case it would be running so that directly the latest values are considered.
The backgriound work itself does not do any cancleing.
When you call bgw.CancelAsync it sets a flag on the background worker that you need to check yourself in the DoWork handler.
something like:
bool _restart = false;
private void button1_Click(object sender, EventArgs e)
{
bgw.CancelAsync();
_restart = true;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 300; i++)
{
if (bgw.CancellationPending)
{
break;
}
//time consuming calculation
}
}
private void bgw_WorkComplete(object sender, eventargs e) //no ide to hand not sure on name/args
{
if (_restart)
{
bgw.RunWorkerAsync();
_restart = false;
}
}
There are a couple of options, it all depends on how you want to skin this cat:
If you want to continue to use BackgroundWorker, then you need to respect the model that has been established, that is, one of "progress sensitivity". The stuff inside DoWork is clearly required to always be aware of whether or not the a pending cancellation is due (i.e., there needs to be a certain amount of polling taking place in your DoWork loop).
If your calculation code is monolithic and you don't want to mess with it, then don't use BackgroundWorker, but rather fire up your own thread--this way you can forcefully kill it if needs be.
You can hook the change event handler for the controls in which the values are changed and do the following in the handler:
if(!bgWrkr.IsBusy)
//start worker
else if(!bgWrkr.CancellationPending)
bgWrkr.CancelAsync();
Hope it helps you!
I want to leave my requests running, but no longer care about the results. I override the value of the background worker (my busy spinner is using the isBusy flag).
private void SearchWorkerCreate() {
this.searchWorker = new BackgroundWorker();
this.searchWorker.DoWork += this.SearchWorkerWork;
this.searchWorker.RunWorkerCompleted += this.SearchWorkerFinish;
}
private void SearchWorkerStart(string criteria){
if(this.searchWorker.IsBusy){
this.SearchWorkerCreate();
}
this.searchWorker.RunWorkerAsync(criteria);
this.OnPropertyChanged(() => this.IsBusy);
this.OnPropertyChanged(() => this.IsIdle);
}
May this method help someone... I've created a function to reset the backgroundworker in one method. I use it for task to do periodically.
By creating a Task, the backgroundworker is can be stopped with the CancelAsync and restarted inside the Task. Not making a Task wil start the backgroundworker again before it is cancelled, as the OP describes.
The only requirement is that your code runs through some loop, which checks the CancellationPending every period of time (CheckPerMilliseconds).
private void ResetBackgroundWorker()
{
backgroundWorker.CancelAsync();
Task taskStart = Task.Run(() =>
{
Thread.Sleep(CheckPerMilliseconds);
backgroundWorker.RunWorkerAsync();
});
}
Inside the backgroundworker I use a for-loop that checks the CancellationPending.
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while(true)
{
if (backgroundWorker.CancellationPending)
{
return;
}
//Do something you want to do periodically.
for (int i = 0; i < minutesToDoTask * 60; i++)
{
if (backgroundWorker.CancellationPending)
{
return;
}
Thread.Sleep(CheckPerMilliseconds);
}
}
}

Categories

Resources