Tulsi
Tulsi

Reputation: 151

How to stop Backgroundworker and close the form?

I want to completely stop the BackgroundWorker DoWork() Process while running for closing the form.

I have applied following code but in "this.Invoke" it throws error : "Invoke or BeginInvoke cannot be called on a control until the window handle has been created." while form close.

   private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            var dt_Images = db.Rings.Select(I => new { I.PaidRs, I.TypeID, I.RingID, I.CodeNo, Image = Image.FromStream(new MemoryStream(I.Image.ToArray())) }).OrderByDescending(r => r.TypeID);
            foreach (var dr in dt_Images.ThenByDescending(r => r.RingID).ToList())
            {
                BTN = new Button();
                BTN.TextImageRelation = TextImageRelation.TextAboveImage;
                BTN.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
                BTN.AutoSize = true;
                BTN.Name = dr.RingID.ToString();
                BTN.Image = dr.Image;
                BTN.Text = dr.CodeNo.ToString() + "    " + dr.TypeID.ToString();
                this.Invoke(new MethodInvoker(delegate { if (backgroundWorker1 != null) flowLayoutPanel1.Controls.Add(BTN); else return; }));
                BTN.Click += new EventHandler(this.pic_Click);
                this.Invoke(new MethodInvoker(delegate { if (backgroundWorker1 == null) txt_pcs.Text = flowLayoutPanel1.Controls.Count.ToString(); else return;}));
            }
        }

  private void Form_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Escape)
            {
                backgroundWorker1.CancelAsync(); //backgroundworker doesnt stop here
                backgroundWorker1 = null;   //it still invokes the delegate
                this.Dispose();  
            }
        }

How to solved this error?

please help me.

Upvotes: 2

Views: 6814

Answers (4)

ispiro
ispiro

Reputation: 27673

Here's a simple program which, I think, will give you an idea of how take care of a closing of a form while a BackgroundWorker is running:

int i = 0;//This is to show progress.
bool cancel;//This is to notify RunWorkerCompleted to Close() the Form if needed.

public Form1()
{
    InitializeComponent();
    FormClosing += Form1_FormClosing;

    backgroundWorker1.WorkerSupportsCancellation = true;
    backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
    backgroundWorker1.RunWorkerAsync();
}

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //Since this is executed on the main thread - it is not (as far as I know) going to "race" against the FormClosing.
    if (cancel) Close();
    else Text = "Done";
}

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if (backgroundWorker1.IsBusy)
    {
        cancel = true;
        backgroundWorker1.CancelAsync();
        e.Cancel = true;
    }
}

private void button1_Click(object sender, EventArgs e)
{
    backgroundWorker1.CancelAsync();
}

//This is executed on a separate thread:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (!backgroundWorker1.CancellationPending)
    {
        Invoke((Action)(() => Text = (i++).ToString()));
        Thread.Sleep(1000);
    }
    e.Cancel = true;
}

Upvotes: 1

vgru
vgru

Reputation: 51224

The actual problem is that there is no way to safely check if form is being closed before calling Invoke in a background thread.

To remedy this, what you can do is to postpone closing a bit, until the background thread has the chance to exit the main loop.

First, declare two flags and a lock object:

private volatile bool _closeRequest = false;
private volatile bool _workerStopped = false;
private readonly object _lock = new object();

Then, when you want to close the form, simply call Close:

private void Form_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyData == Keys.Escape)
    {
        Close();
    }
}

Inside FormClosing, check if worker has already stopped. This part must be inside a monitor, to prevent a race-condition when background thread is just being finished (i.e. to ensure that _workerStopped and _closeRequest are updated atomically):

protected override void OnFormClosing(FormClosingEventArgs e)
{
    lock (_lock)
    {
        // if not stopped
        if (!_workerStopped)
        {
            // delay closing
            e.Cancel = true;

            // notify worker
            _closeRequest = true;
        }
    }
    base.OnFormClosing(e);
}

Finally, in your background thread, actually close the form if needed:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        foreach (var dr in GetImages())
        {
            if (_closeRequest)
                break;

            // ... do stuff
        }
    }
    finally
    {
        lock (_lock)
        {
            // notify we stopped
            _workerStopped = true;

            // if closing was postponed, close now
            if (_closeRequest)
                BeginInvoke(new MethodInvoker(Close));
        }
    }
}

Upvotes: 1

Israel Lot
Israel Lot

Reputation: 653

You have to watch for the cancel request inside your worker. Like

private void DoWork(object sender, DoWorkEventArgs e) 
{ 
 var worker = sender as BackgroundWorker; 

 while (!worker.CancellationPending) 
 { 
   … 
 } 

 if (worker.CancellationPending) 
 { 
  e.Cancel = true; 
 } 
}

Upvotes: 2

Philip Fourie
Philip Fourie

Reputation: 116857

Inside your DoWork method you have to check against backgroundWorker1.CancellationPending before starting to process the next image.

You have to waiting until the last image is processed before allowing the user to close the form.

For a comlete example of how to use CancellationPending and Cancel properties see this MSDN example

Upvotes: 0

Related Questions