After canceling the BackGroundWorker, in the DoWork, the CancellationPending is true but when he comes to the RunWorkerCompleted, the CancellationPending is false. I dont know what did I do wrong?
static BackgroundWorker b1;
static void Main(string[] args)
{
b1=new BackgroundWorker();
b1.DoWork += new DoWorkEventHandler(work1);
b1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(completed);
b1.WorkerSupportsCancellation = true;
b1.RunWorkerAsync("Hellow");
Console.ReadLine();
}
private static void completed(object sender, RunWorkerCompletedEventArgs e)
{
if (((BackgroundWorker)sender).CancellationPending)
Console.WriteLine("Canceled!");
else
Console.WriteLine("Result:" + e.Result);//it goes here every time
}
private static void work1(object sender, DoWorkEventArgs e)
{
((BackgroundWorker)sender).CancelAsync();
if (((BackgroundWorker)sender).CancellationPending)
{
e.Cancel = true;
}
}
By the way, How can I add an error that occur in the DoWork to the RunWorkerCompletedEventArgs.Error for shoing it up to the user?
Yes, the BackgroundWorker class sets the CancellationPending property to false before raising the RunWorkerCompleted event. Whether or not the worker was actually cancelled.
This is quite intentional, it stops you from falling into a nasty trap that's always around when you use threads. Code that uses threads often misbehaves randomly and unpredictably due to a kind of bug called "threading race". It is a very common kind of bug and dastardly difficult to debug.
What can easily go wrong in your intended approach if BGW didn't do this is that you'll assume that the worker got cancelled when you see CancellationPending set to true. But that's an illusion, you cannot tell the difference between it being cancelled and it completing normally. The corner case is you calling CancelAsync() a microsecond before the worker completes. The worker never has a chance to even see the CancellationPending flag set to true, it was busy finishing the last bits of the DoWork event handler method. That's a threading race, the worker raced ahead of your call and completed normally.
The proper hand-shake that avoids this bug is your worker setting e.Cancel to true when it sees the CancellationPending property set to true. And of course stopping what's its doing. Now it is reliable, the e.Cancelled property in the RunWorkerCompleted event handler is a copy of e.Cancel. So your code can now reliably tell you whether or not the worker saw the cancel request.
I believe the CancellationPending property is for use during the background operation (in your work1 method). It will tell the background worker that you have requested the background operation be canceled. Once the RunWorkerCompleted event is called, the background worker has done the work to cancel the request, and therefore the cancellation is no longer pending.
EDIT: the RunWorkerCompletedEventArgs has a Cancelled property that will tell you if the background operation was cancelled.
If you throw an exception from the DoWork method (work1 in your case), it should be caught by the BackgroundWorker and populate the Error property of the RunWorkerCompletedEventArgs.
Related
I'm using a background worker to handle the loading of a file to stop my ui from freezing however it seems that the RunWorkerCompleted is finishing before my DoWork event has completed (Causes errors when exiting dialog)... is there anything I'm doing wrong? Am I better off doing this over a task?
public static <T> LoadDesign(string xmlPath)
{
PleaseWait pw = new PleaseWait(xmlPath);
pw.ShowDialog();
return pw.design;
}
private PleaseWait(string xmlFile)
{
InitializeComponent();
bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += (s, e) =>
{
design = (Cast)DllCall containing XmlSerializer.Deserialize(...,xmlFile);
};
bw.RunWorkerCompleted += (s, e) => {
//Exit please wait dialog
this.Close();
};
if (!bw.IsBusy)
bw.RunWorkerAsync();
}
I believe the issue may be down to the fact that my background worker is calling a dll and not waiting for the response. I've tried to add checks such as while(design == null) to no avail..
Edit2
The error is NRE as the design hasn't been loaded, I can easily fix this but would rather get the threading working instead.
There are lots of little mistakes. Given that we are probably not looking at the real code and that we don't have a debugger with a Call Stack window to see where it actually crashes, any one of them might be a factor.
Testing bw.IsBusy and not starting the worker when it is true is a grave mistake. It can't ever be busy in the code as posted but if it actually is possible for it to be true then you've got a nasty bug in your code. Since you actually did subscribe the events on a busy worker. Now the RunWorkerCompleted event handler will run twice.
Using the Close() method to close a dialog is not correct. A dialog should be closed by assigning its DialogResult property. Not the gravest kind of mistake but wrong nonetheless.
There's a race in the code, the worker can complete before the dialog is ever displayed. A dialog can only be closed when its native window was created. In other words, the IsHandleCreated must be true. You must interlock this to ensure this can never happen. Subscribe the dialog's Load event to get the worker started.
You blindly assume that the worker will finish the job and produce a result. That won't be the case when its DoWork method died from an exception. Which is caught by BackgroundWorker and passed to the RunWorkerCompleted event handler as the e.Error property. You must check this property and do something reasonable if it isn't null.
Judging from the comments, I'd guess at the latter bullet being the cause. You debug this by using Debug + Exceptions, tick the Thrown checkbox for CLR exceptions. The debugger will now stop when the exception is thrown, allowing you to find out what went wrong.
It maybe possible your background worker actually does not take much time and complete before the dialog is shown. I'd suggest shift the background worker initialization and start up code to PleaseWait's Form_Load or Form_Shown
If you Call another async function in your BackgroundWorker _DoWork event,
like;
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
somethingsToDoAsync();
// somethingsToDoAsync() function is to ASYNC
}
_RunWorkerCompleted fires even before completed _Dowork event.
Change other somethingsToDoAsync() function to not async.
I am having a problem for a while
this line:
txtPastes.Text = (string)e.UserState;
throws a cross thread exception and I didn't find any solution
txtPastes - textbox
GuiUpdate - BackgroundWorker
lstAllPastes - list of string
private void GuiUpdate_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
while (lstAllPastes.Count == 0) ;
for (int i = 0; i < lstAllPastes[0].Length; i++)
{
GuiUpdate.ReportProgress(0, lstAllPastes[0].Substring(0, i));
Thread.Sleep(1);
}
lstAllPastes.RemoveAt(0);
}
}
private void GuiUpdate_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
txtPastes.Text = (string)e.UserState;
}
private void GuiUpdate_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
You cannot update a UI control from any thread other than the UI thread. Typically, the BackgroundWorker would take care of raising its ProgressChanged and RunWorkerCompleted events correctly on the UI thread. Since that doesn’t appear to be the case here, you could marshal your UI-updating logic to the UI thread yourself by using the Invoke method:
txtPastes.Invoke(new Action(() =>
{
// This code is executed on the UI thread.
txtPastes.Text = (string)e.UserState;
}));
If you’re on WPF, you would need to call Invoke on the control’s dispatcher:
txtPastes.Dispatcher.Invoke(new Action(() =>
{
txtPastes.Text = (string)e.UserState;
}));
Update: As Thomas Levesque and Hans Passant have mentioned, you should investigate the reason why your ProgressChanged event is not being raised on the UI thread. My suspicion is that you’re starting the BackgroundWorker too early in the application initialization lifecycle, which could lead to race conditions and possibly a NullReferenceException if the first ProgressChanged event is raised before your txtPastes textbox has been initialized.
Well, this is supposed to work of course. The cause of this exception is not visible in your snippet. What matters is exactly where and when the BackgroundWorker is started. Its RunWorkerAsync() method uses the SynchronizationContext.Current property to figure out what thread needs to execute the ProgressChanged event handler.
This can go wrong when:
You started the BGW too early, before the Application.Run() call. Winforms or WPF won't yet have had a chance to install its own synchronization provider.
You called the BGW's RunWorkerAsync() method in a worker thread. Only marshaling to the UI thread is supported, the message loop is the crucial ingredient to make running code on another thread work.
The form that has txtPastes control was created on another thread. With the BGW started on the UI thread that's still a thread mismatch
The form's Show() method was called on another thread. Which creates the native Windows window on the wrong thread.
Make sure you start the BackgroundWorker from the UI thread; if you do that, the ProgressChanged event will be raised on that thread, and the exception won't happen.
If you want to update yout GUI for example TextBox, you should read this article:
Update GUI from another thread
In my program, there will be a time where I have to call
Thread.Sleep(Timeout.Infinite);
to "temporarily pause" the BackgroundWorker. A groupbox will be shown before the backgroundworker thread sleeps. That groupbox has a button that should "wake up" the backgroundworker thread and continue it.
If I call Thread.Interrupt() (which by the way I can't seem to use unless I create a Thread object, which I shouldn't do) at the button_Click event, like:
private void button1_Click(object sender, EventArgs e)
{
Thread.Interrupt(); //interrupt thread
}
The Thread it "would" interrupt is the UI Thread, am I right? What I need to do is to Interrupt the BackgroundWorker thread. How do I do this?
EDIT: Thanks for those that replied to this question. I'll use AutoResetEvent. Seems more appropriate for my use.
Let me start with the high-level concept:
What you should do is have a token that you check every so often in the code that is being executed by the BackgroundWorker. When the token is set your background code will stop the normal flow and just check the token every now and then and when the token is cleared the background code can continue processing.
So the interesting part above is the token. What I would do is maybe have a boolean that I check and when that boolean is set to true I would block the thread by waiting on ManualResetEvent. When you want to resume the processing you set the boolean to false and use the Set() method of the ManualResetEvent to release allow the code to continue.
You have to look at ManualResetEvent
Usage:
ManualResetEvent e = new ManualResetEvent(false); //global variable
e.WaitOne(); // Thread will wait until event is triggered
e.Set(); // Trigger event from other thread
You should use a semaphore to sync between the threads.
once you want the background worker to "sleep", grap a handle on the semaphore, once you click on the "wake up" button, release the semaphore and the background worker will resume..
From your GUI thread (the one that shows the button) your should declare
Semaphore s = new Semaphore(0, 1);
on the background worker thread - this statement initialize a semaphore with a default value of 0 (locked)
on your backgroundworker thread/code call:
s.WaitOne();
this statement actually cause the background worker to wait until the semaphore is released by the gui thread (your wake up button).
on the button click handler, call the:
s.Release();
the release operation allows the background worker code to resume running.
Need help to stop the BackgroundWorker thread.
I am trying to stop a background worker thread. This is what I am doing:
On stop button click (UI layer):
if (backgroundWorker.IsBusy == true &&
backgroundWorker.WorkerSupportsCancellation == true)
{
backgroundWorker.CancelAsync();
}
On DoWork event (UI layer):
if ((backgroundWorker.CancellationPending == true))
{
e.Cancel = true;
}
else
{
//Function which progresses the progress bar is called
//here with its definition in business layer
}
Once the DoWork event is fired and my program control is in the function defined in Business layer, how do I revert back to the DoWork event to set ‘e.Cancel = true’?
Setting e.Cancel does nothing, if CancellationPending is true you need to basically break out of DoWork() using return or whatever (after you've stopped what you're doing).
Something like:
private void DoWork(...)
{
// An infinite loop of work!
while (true)
{
// If set to cancel, break the loop
if (worker.CancellationPending)
break;
// Sleep for a bit (do work)
Thread.Sleep(100);
}
}
DoWork() executes in a seperate thread to the UI thread, you can report back to the UI thread using BackgroundWorkr.ReportProgress().
DoWork will run in it's own thread and is not dependant of the GUI thread.
You do almost everything correct. From the GUI thread, set the CancellationPending to true.
In the DoWork method, you probably have a loop of some sort.
Here you check if CancellationPending == true, but in addition to setting e.Cancel to true, also include a return call to make the method return and effectively stopping the worker. This also causes the WorkerCompleted event to fire on the GUI thread if the method is hooked up.
If the DoWork method perform some long task that is not divided into parts (for example if your DoWork method looks like this:
void DoWork( (object sender, DoWorkEventArgs e)
{
myClass.SomeLongOperation();
}
Then you are out of luck, since you need to manually check the CancellationPending inside the DoWork method to be able to stop it. If the DoWork itself "hangs" and waits for a operation you can't control, you can't stop it (in any orderly fashion) by setting CancellationPending from the GUI thread.
Once the DoWork event is fired and my program control is in the function defined in Business layer, how do I revert back to the DoWork event to set ‘e.Cancel = true’?
You don't. If you want cancellation to be possible during execution of your business layer, then your business layer must support cancellation. So, you have two options:
In your DoWork method, call only short-time business layer methods and check for CancellationPending in between.
Make your business layer methods cancellation-aware, i.e., pass the BackgroundWorker to them and have them periodically check CancellationPending (and retun, once it turns true).
Keep checking the CancellationPending=True Flag in the else part of your logic, and return when true.
The code e.Cancel = true only sets a state on the BackgroundWorker, so that it knows it has been cancelled, it doesn't actually cancel the process.
You'll have to check the CancellationPending inside the loop of your method and break it or return.
void DoWork(object sender, DoWorkEventArgs e) {
for (int i = 0; i < length; i++) {
if(e.CancellationPending) {
return;
}
// Long running code
}
}
Normally, when I want to cancel a backgroundWorker in C# I will do something like this:
while (backgroundWorker1.IsBusy)
{
backgroundWorker1.CancelAsync();
autoResetEvent1.WaitOne(BGW_CANCEL_TIMEOUT);
}
In one application there are a number of backgroundWorkers. Is it valid to use a helper-function to cancel the backgroundWorkers, like so?
CancelBackgroundWorker(BackgroundWorker bgw, AutoResetEvent are)
{
while (bgw.IsBusy)
{
bgw.CancelAsync();
are.WaitOne(BGW_CANCEL_TIMEOUT);
}
}
My concern is that instead of passing the objects in question into the function, copies are made, thereby defeating the purpose of having the function. The main purpose of having the function is to reduce code space and make the application code more readable/maintainable.
No, this isn't okay. It causes deadlock when the BGW has a RunWorkerCompleted event handler. That handler cannot run until the main thread goes idle and re-enters the message loop. The IsBusy property will stay True until that event handler completes.
You have a time-out on the WaitOne call so at least your program won't hang completely. But when WaitOne() returns, the BGW is not yet completed, the RWC event handler hasn't run yet. The only practical alternative is for the RWC event handler to do whatever needs done when the cancellation is complete.
Your code is wrong.
Calling CancelAsync merely sets the CancellationPending flag on the BackgroundWorker to true. Your code in the DowWork event is expected to periodically check this flag and stop if the flag is true.
Calling CancelAsync many times will not do any good, and there shouldn't be any reason to freeze the UI thread until it actually cancels.