BackgroundWorker.Cancel only works with DoEvents()

  • Thread starter Thread starter Tom P.
  • Start date Start date
T

Tom P.

I have an application that is using a BackgroundWorker but the worker
can get interrupted if the user wants to move on (it pulls a directory
list but the user can navigate away if they want).

When the user navigates I check _worker.IsBusy and is it's true I call
_worker.CancelAsync().

Then I have a wait loop until the worker is no longer busy:
while(_worker.IsBusy)
{
//Here's where my problem is
}

If I use Thread.Sleep(0) or just leave the loop empty, IsBusy never
gets set to false. But is I use DoEvents it gets set and all the
cancellation works fine. I'm not a big fan of DoEvents (hence the use
of a BackgroundWorker object) but how do I get this thing to work
right? How do I get the background thread to release without using
DoEvents?

Tom P.
 
Tom said:
I have an application that is using a BackgroundWorker but the worker
can get interrupted if the user wants to move on (it pulls a directory
list but the user can navigate away if they want).

When the user navigates I check _worker.IsBusy and is it's true I call
_worker.CancelAsync().

Don't check IsBusy. There's no point in doing so. Just call
CancelAsync(). If the worker is still busy, it should get canceled, and
if it's not then canceling it shouldn't be a problem.

More importantly, the IsBusy property value could easily change between
the time you check it and the time you call CancelAsync(). It doesn't
tell you anything at all useful about what the state of things are at
the point in time you'll call CancelAsync().
Then I have a wait loop until the worker is no longer busy:
while(_worker.IsBusy)
{
//Here's where my problem is
}

Is that in the GUI thread? Don't do that. More generally, don't wait
on threads at all. If you have something that has to happen when the
thread is done, set up some kind of signaling mechanism to do that,
rather than having some thread wait for the other.

A "signaling mechanism" can be as simple as the worker code calling
Control.Invoke() with a delegate instance that will do some kind of
state change required at the "not busy" point of the work.
If I use Thread.Sleep(0) or just leave the loop empty, IsBusy never
gets set to false. But is I use DoEvents it gets set and all the
cancellation works fine.

BackgroundWorker has to raise the RunWorkerCompleted event before it can
be "not busy". But the event is raised on the GUI thread (that's the
whole point of BackgroundWorker), and that can't happen if your GUI
thread is stuck waiting.
I'm not a big fan of DoEvents (hence the use
of a BackgroundWorker object) but how do I get this thing to work
right? How do I get the background thread to release without using
DoEvents?

Don't wait on the BackgroundWorker at all. If you have something that
has to happen when the BackgroundWorker is done, put that in your
RunWorkerCompleted event handler (preferred), or have the
BackgroundWorker DoEvent handler do some the "something" that has to
happen when it's done (not preferred, but it can work).

Pete
 
BackgroundWorker has to raise the RunWorkerCompleted event before it can
be "not busy".  But the event is raised on the GUI thread (that's the
whole point of BackgroundWorker), and that can't happen if your GUI
thread is stuck waiting.

That's exactly what I wanted to know. That clears up everything.
Don't wait on the BackgroundWorker at all.  If you have something that
has to happen when the BackgroundWorker is done, put that in your
RunWorkerCompleted event handler (preferred), or have the
BackgroundWorker DoEvent handler do some the "something" that has to
happen when it's done (not preferred, but it can work).

Pete

Good point. There is nothing served by my waiting around to make sure
the thread is canceled. I should just call CancelAsync() and go about
my business knowing the thread will get canceled.

Thanks for setting me straight.
Tom P.
 
BackgroundWorker has to raise the RunWorkerCompleted event before it can
be "not busy".  But the event is raised on the GUI thread (that's the
whole point of BackgroundWorker), and that can't happen if your GUI
thread is stuck waiting.


Don't wait on the BackgroundWorker at all.  If you have something that
has to happen when the BackgroundWorker is done, put that in your
RunWorkerCompleted event handler (preferred), or have the
BackgroundWorker DoEvent handler do some the "something" that has to
happen when it's done (not preferred, but it can work).

Pete

Almost. If I try to reuse the background Worker too soon, it will call
DoWork (for the new thread) before RunWorkerCompleted (from the old
thread) finishes and the new thread will think it was canceled before
it gets any work done.

Should I create a new BackgroundWorker object after canceling the old
one?

Tom P.
 
Tom said:
Almost. If I try to reuse the background Worker too soon, it will call
DoWork (for the new thread) before RunWorkerCompleted (from the old
thread) finishes and the new thread will think it was canceled before
it gets any work done.

That doesn't make sense. Calling RunWorkerAsync() resets the
CancellationPending flag; if you are finding it still set in your DoWork
event handler, the only way for that to happen is for some code
somewhere to actually cancel the worker _after_ you've called
RunWorkerAsync().
Should I create a new BackgroundWorker object after canceling the old
one?

It's fine to reuse the BackgroundWorker instance. Just don't do it
until the first task is done.

As I wrote before, you don't need to have your thread wait for the
worker. Just put whatever logic you want executed when it's done in the
RunWorkerCompleted event handler. If you do it correctly, it will work
fine. If you don't do it correctly, well…all sorts of things can go
wrong. :)

See below for a short code example of doing it correctly. It needs
project references to the System, System.Drawing, and
System.Windows.Forms libraries.

Pete



using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;

namespace TestBackgroundWorkerRestart
{
class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}

public class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private bool _fContinue;
private BackgroundWorker _worker;

private void button1_Click(object sender, EventArgs e)
{
_fContinue = true;

button1.Enabled = false;
button2.Enabled = true;
button3.Enabled = true;

_worker = new BackgroundWorker();

_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;

_worker.ProgressChanged += (sender1, e1) =>
{
textBox1.AppendText((string)e1.UserState);
};

_worker.DoWork += (sender1, e1) =>
{
bool fReported = false;

while (!_worker.CancellationPending)
{
if (!fReported)
{
_worker.ReportProgress(0, "Worker started...no
cancellation pending\n");
fReported = true;
}

Thread.Sleep(250);
}

if (!fReported)
{
_worker.ReportProgress(0, "Worker cancelled before
entering loop\n");
}
};

_worker.RunWorkerCompleted += (sender1, e1) =>
{
textBox1.AppendText("Worker completed\n");

if (_fContinue)
{
_worker.RunWorkerAsync();
}
else
{
textBox1.AppendText("Not restarting worker\n");

_worker = null;

button1.Enabled = true;
button2.Enabled = false;
button3.Enabled = false;
}
};

_worker.RunWorkerAsync();
}

private void button2_Click(object sender, EventArgs e)
{
_worker.CancelAsync();
}

private void button3_Click(object sender, EventArgs e)
{
_fContinue = false;
_worker.CancelAsync();
}


#region Code from Form1.Designer.cs

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "Start Worker";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new
System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Enabled = false;
this.button2.Location = new System.Drawing.Point(93, 12);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(90, 23);
this.button2.TabIndex = 1;
this.button2.Text = "Cancel Worker";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new
System.EventHandler(this.button2_Click);
//
// button3
//
this.button3.Enabled = false;
this.button3.Location = new System.Drawing.Point(189, 12);
this.button3.Name = "button3";
this.button3.Size = new System.Drawing.Size(75, 23);
this.button3.TabIndex = 2;
this.button3.Text = "Stop Worker";
this.button3.UseVisualStyleBackColor = true;
this.button3.Click += new
System.EventHandler(this.button3_Click);
//
// textBox1
//
this.textBox1.Anchor =
((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(12, 41);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.ScrollBars =
System.Windows.Forms.ScrollBars.Both;
this.textBox1.Size = new System.Drawing.Size(335, 209);
this.textBox1.TabIndex = 3;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(359, 262);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button3);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button3;
private System.Windows.Forms.TextBox textBox1;

#endregion
}
}
 
Back
Top