When the user tries to double click button, the backgroundWorker will initiate twice.
is this a good workaround?
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Some processing here
Thread.Sleep(1000);
button1.Invoke((MethodInvoker)delegate { button1.Enabled= true; });
}
// Run backgroundProcess
private void button1_Click(object sender, EventArgs e)
{
this.button1.Enabled = false;
backgroundWorker1.CancelAsync();
backgroundWorker1.RunWorkerAsync();
}
You can use backgroundWorker1_RunWorkerCompleted event to enable button1. instead for enabling then in backgroundWorker1_DoWork
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.button1.Enabled = true;
//rest of operations
}
What is wrong with your code:
You ware enabling the button in _DoWork that is meaning less since backgroundWorker1.RunWorkerAssync(); will call _DoWork() there the button enables and hence the second click also get executed.
You have to take care of three basic things when you are going to use BackgrounWorker.
DoWork: You can handle execution of your business logic and cancellation of your work if required.
RunWorkerCompleted: You can handle post execution task. Here you want to enable button, so you should write appropriate code into RunWorkerCompleted Event.
Handle the WorkerSupportsCancellation property if you want to cancel backgroundworker process at any point of execution.
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
button1.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Invoke((MethodInvoker)delegate { button1.Enabled = true; });
backgroundWorker1.DoWork -= backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted -= backgroundWorker1_RunWorkerCompleted;
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
AppendTextBox( "Hit: " + i.ToString() + Environment.NewLine);
if (backgroundWorker1.CancellationPending)
{
break;
}
}
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
}
}
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
textBox1.Text += value;
}
private void button2_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy)
backgroundWorker1.CancelAsync();
}
If the worker is actually running, then almost certainly not.
Cancellation is cooperative. Calling CancelAsync just sets the CancellationPending flag to true. It is up to the background worker to periodically check this flag and respond to cancellation.
The chances that a running worker has gotten to an appropriate point to check that flag, checked it, and responded, all within the gap of time between these two lines of code:
backgroundWorker1.CancelAsync();
backgroundWorker1.RunWorkerAsync();
Is extremely slim. It's more likely (if the worker was running) that RunWorkerAsync will throw InvalidOperationException since the worker is still running.
Related
In a WinForm application, I have an On/Off switch for a background process.
On Click, the program launch a process and restart it when it's finish, till you use the Off switch.
The following code is the working attempt that had multiple issues.
From Damien_The_Unbeliever comments:
suspend threads which means that they stay around forever, and implementing looping via recursion which could easily cause a stack overflow.
public partial class frmMain
{
Thread thread;
bool isRunning = false;
public frmMain()
{
InitializeComponent();
}
private void OnOffSwitch_Click(object sender, EventArgs e)
{
if (!isRunning)
{
btnSwitch.Text = "Stop";
isRunning = true;
thread = new Thread(doLoop);
thread.IsBackground = true;
thread.Start();
}
else
{
if (thread.IsAlive)
thread.Suspend();
btnSwitch.Text = "Start";
isRunning = false;
}
}
public void doLoop()
{
ClearScreenLogic.Run();
if (AutoReconnect)
ReconnectLogic.Run();
// Etc..
doLoop();
}
I am trying to switch from this working solution to background worker.
Implement your doLoop in the DoWork event of the BackGroundWorker and make sure you handle cancelation. Make sure to set the properties of your backgroundworker to WorkerReportprogress and WorkerSupportCancellation to true;
This is what you would need:
private void button1_Click(object sender, EventArgs e)
{
// on and off
if (backgroundWorker1.IsBusy)
{
// cancel if we have not already done so
if (!backgroundWorker1.CancellationPending)
{
backgroundWorker1.CancelAsync();
}
}
else
{
// start the background work
button1.BackColor = Color.Yellow;
backgroundWorker1.RunWorkerAsync();
}
}
// this runs on a background thread
// do not do stuff with the UI here
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int progress = 0;
// stop looping if cancellation is requested
while (!backgroundWorker1.CancellationPending)
{
// make it nice
backgroundWorker1.ReportProgress(progress);
ClearScreenLogic.Run();
if (AutoReconnect)
ReconnectLogic.Run();
// Etc..
progress++; // for feedback
}
}
// tell the use something is going on, this runs on the UI thread
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.ProgressPercentage.ToString();
}
// we're done, tell the user so
// this runs on the UI thread
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.BackColor = Color.Green;
label1.Text = "cancelled";
}
When implemented correctly your users will see something like this:
This is the code that I am trying to execute, but stepping through my code I never see any progress indicated or updated on my windows form showing progressbar1. This is my 1st attempt in getting a background worker to function properly, and all I have is a windows form with one button on it and this is all of the code involved in the project.
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private int i = 0;
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = false;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
ReadySteadyGo();
worker.ReportProgress((i * 10));
FinalizeAndFinish();
worker.ReportProgress((i * 10));
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Text = "Done!";
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Text = (e.ProgressPercentage.ToString() + "%");
}
private void ReadySteadyGo()
{
Thread.Sleep(100000);
}
private void FinalizeAndFinish()
{
Thread.Sleep(1000);
}
}
It appears that you are using Thread.Sleep() to simulate a long-running operation. There are a few things you should consider based on your code example:
When the backgroundWorker1.RunWorkerAsync(); is executed, it starts working on another thread. Thus, if you are debugging interactively and you have not set a breakpoint in the backgroundWorker1_DoWork method, you are not likely to see this code execute.
When the Thread.Sleep(100000) executes, it essentially means that the background worker will pause for 100 seconds - so you need to make sure you are waiting at least that long to see the UI updated.
Also, as per Hans Passant's comment, consider the following:
Nor can you see it doing anything, there's no point to assigning the
ProgressBar.Text property since it doesn't display text. Set Value
instead.
I recreated your example in Visual Studio and am hitting a breakpoint in backgroundWorker1_DoWork so the multi-threading is working properly, you just need to do proper processing?
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
backgroundWorker1.ReportProgress(i);
Thread.Sleep(100);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
In MainPage.xaml.cs I have created a BackgroundWorker. This is my code:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
bgw = new BackgroundWorker();
bgw.WorkerSupportsCancellation = true;
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.RunWorkerAsync();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
bgw.CancelAsync();
base.OnNavigatedFrom(e);
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
if ((sender as BackgroundWorker).CancellationPending)
{
e.Cancel = true;
return;
}
Thread.Sleep(1000*60*5); // 5 minutes
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled || (sender as BackgroundWorker).CancellationPending)
return;
/* the work thats needed to be done with the ui thread */
(sender as BackgroundWorker).RunWorkerAsync();
}
But this does not work. How can i properly stop the backgroundworker when navigating to another page?
Create a signal such as ManualResetEvent.
ManualResetEvent _evStop = new ManualResetEvent(false);
Instead of doing Thread.Sleep(), wait on your event object for your desired "delay" time.
_evStop.WaitOne(1000*60*5, false);
When you want to stop processing early, raise the signal on your event object.
_evStop.Set();
When the event is signalled, your WaitOne will return early. Otherwise, it will time out after your specified amount of time.
This is how I did it in my code:
In the backgroundWorker DoWork event I did:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
_busy.WaitOne();
this.Invoke(new MethodInvoker(delegate { label2.Text = "Website To Crawl: "; }));
this.Invoke(new MethodInvoker(delegate { label4.Text = mainUrl; }));
webCrawler(mainUrl, levelsToCrawl, e);
}
Then in the pause button click event I did:
private void button4_Click(object sender, EventArgs e)
{
_busy.Reset();
}
In the resume button click event I did:
private void button5_Click(object sender, EventArgs e)
{
_busy.Set();
}
But it's not working when I click to start the process:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
button1.Enabled = false;
this.Text = "Processing...";
label6.Text = "Processing...";
label6.Visible = true;
button2.Enabled = false;
checkBox1.Enabled = false;
checkBox2.Enabled = false;
numericUpDown1.Enabled = false;
button3.Enabled = true;
}
Nothing happen only when I click the resume button the process start then when I click the pause button nothing happen.
I want that when I click the start process button it will start the backgroundWorker regular then when clicking the pause button it will pause and the resume button it will resume.
What did I do wrong ? Can someone fix my code ?
In your BackgroundWorker thread code, you need to find places that are safe to "pause" execution. The ManualResetEvent is the right way to code. This other post might help:
Is there a way to indefinitely pause a thread?
Basically, in a few choice points in your worker thread code where you want to allow it to pause, try inserting:
_busy.WaitOne(Timeout.Infinite);
And when you want to pause (from your main thread) you use:
_busy.Reset();
And to resume:
_busy.Set();
You should be able to do this using the ManualResetEvent like this ...
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
_busy.WaitOne();
test(mainUrl, levelsToCrawl, e);
}
... and then when you want to pause the thread call _busy.Reset() ... and when you want to restart it call _busy.Set().
Additionally, you can place _busy.WaitOne(); anywhere you want to pause.
I've been looking for the answer of this thread but I come up with my own solution i made and i just wanna share it with you. hope this works.
I have a background worker and i want to pause it when i hit close button of my form. asking "You are about to cancel the process" so it should pause the process.
declare bool pauseWorker = false; on your class.
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (condition)
{
if (pauseWorker == true)
{
while (pauseWorker == true)
{
if (pauseWorker == false) break;
}
}
else
{
//continue process... your code here
}
}
}
private void frmCmsnDownload_FormClosing(object sender, FormClosingEventArgs e)
{
if (bgWorker.IsBusy)
{
pauseWorker = true; //this will trigger the dowork event to loop that
//checks if pauseWorker is set to false
DiaglogResult x = MessageBox.Show("You are about cancel the process", "Close", MessageBoxButtons.YesNo);
if (x == DialogResult.Yes) bgWorker.CancelAsync();
else
{
e.Cancel = true;
pauseWorker = false; //if the user click no
//the do work will continue the process
return;
}
}
}
Therefore the main solution here is the boolean declaration that controls the DoWork event of BGWorker.
Hope this solution helps your problem. Thank you.
I use a simple class that utilizes System.Thread.Monitor and lock()...
public class ThreadPauseState {
private object _lock = new object();
private bool _paused = false;
public bool Paused {
get { return _paused; }
set {
if(_paused != value) {
if(value) {
Monitor.Enter(_lock);
_paused = true;
} else {
_paused = false;
Monitor.Exit(_lock);
}
}
}
}
public void Wait() {
lock(_lock) { }
}
}
Using it is very simple...
private ThreadPauseState _state = new ThreadPauseState();
private void btnPause_Click(object sender, EventArgs e) {
_state.Paused = true;
}
private void btnResume_Click(object sender, EventArgs e) {
_state.Paused = false;
}
private void btnCancel_Click(object sender, EventArgs e) {
backgroundWorker1.CancelAsync();
_state.Paused = false; // needed if you cancel while paused
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
var worker = (BackgroundWorker)sender;
for(var _ = 0; _ < 100; _++) {
_state.Wait();
if(worker.CancellationPending) return;
Thread.Sleep(100); // or whatever your work is
}
}
This works for me:
bool work = true;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += myChangeFunction;
backgroundWorker1.RunWorkerAsync();
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true && work)
{
// Your code here
backgroundWorker1.ReportProgress(0);
Thread.Sleep(1000);
}
e.Cancel = true;
}
private void myChangeFunction(object sender, ProgressChangedEventArgs e)
{
// Here you can change label.text or whatever thing the interface needs to change.
}
private void Stop()
{
work = false;
}
private void Start()
{
work = true;
backgroundWorker1.RunWorkerAsync();
}
NOTE: If you want to change something of the interface, you have to put it in the myChangeFunction(), because in the DoWork() function will not work. Hope this helps.
I know it has 3 methods. In my program I have a method to send a message. It is often late and the program sometimes doesn't send the message at all in response to a button press. At times it is as late as 5 seconds from what I would expect and the program freezes. I want to use a BackgroundWorker to send the message as expected and allow the program to run normally at all times. I had the code for sending the message in a button handler. Now where do I put this equivalent code? I would like all of this to still be handled by a button press.
Is this the appropriate handler?
backgroundWorker1.RunWorkerAsync();
and in:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {}
I'm going to put my code in the button handler?
And this before:
carga.progressBar1.Minimum = 0;
carga.progressBar1.Maximum = 100;
Carga is my other form where the ProgressBar is. How do I use a BackgroundWorker in this scenario?
You can update progress bar only from ProgressChanged or RunWorkerCompleted event handlers as these are synchronized with the UI thread.
The basic idea is. Thread.Sleep just simulates some work here. Replace it with your real routing call.
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.WorkerReportsProgress = true;
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
I know this is a bit old, but in case another beginner is going through this, I'll share some code that covers a bit more of the basic operations, here is another example that also includes the option to cancel the process and also report to the user the status of the process. I'm going to add on top of the code given by Alex Aza in the solution above
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted; //Tell the user how the process went
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true; //Allow for the process to be cancelled
}
//Start Process
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
//Cancel Process
private void button2_Click(object sender, EventArgs e)
{
//Check if background worker is doing anything and send a cancellation if it is
if (backgroundWorker1.IsBusy)
{
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
backgroundWorker1.ReportProgress(i);
//Check if there is a request to cancel the process
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
}
//If the process exits the loop, ensure that progress is set to 100%
//Remember in the loop we set i < 100 so in theory the process will complete at 99%
backgroundWorker1.ReportProgress(100);
}
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
lblStatus.Text = "Process was cancelled";
}
else if (e.Error != null)
{
lblStatus.Text = "There was an error running the process. The thread aborted";
}
else
{
lblStatus.Text = "Process was completed";
}
}