Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BackgroundWorker: InvalidOperationException in RunWorkerCompleted

I have a WinForm with a backgroundWorker:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SeoTools.Utils;

namespace SeoTools.UI
{
    public partial class UIProgress : Form
    {
        public UIProgress(DoWorkEventHandler doWorkEventHandler, RunWorkerCompletedEventHandler runWorkerCompletedEventHandler)
        {
            InitializeComponent();
            this.backgroundWorker.WorkerReportsProgress = true;
            this.backgroundWorker.WorkerSupportsCancellation = true;
            this.backgroundWorker.DoWork += doWorkEventHandler;
            this.backgroundWorker.RunWorkerCompleted += runWorkerCompletedEventHandler;
        }

        public void Start()
        {
            var foo = SynchronizationContext.Current;
            backgroundWorker.RunWorkerAsync();
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            btnStop.Enabled = false;
            btnStop.Text = "Stopping...";
            backgroundWorker.CancelAsync(); 
        }

       private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        try
        {
            wdgProgressBar.Value = e.ProgressPercentage;
            if (this.Visible == false)
            {
                this.ShowDialog();
                this.Update();
            }
        }
        catch (InvalidOperationException) {} 
    }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.Hide(); //Here I get a InvalidOperationException
            this.Dispose();
        }    
    }
}

First time I run this it works fine. But second time I get InvalidOperationException when calling this.Hide().

"Additional information: Cross-thread operation not valid: Control 'UIProgress' accessed from a thread other than the thread it was created on."

The weird thing is on first run foo in Start() is a WindowsFormsSyncronizationContext but on the second try it's a System.Threading.SyncronizationContext.

The application I'm writing is a ExcelDna plugin.

EDIT

Start() is called like this:

 UIProgress uiProgress = new UIProgress(
                delegate(object sender, DoWorkEventArgs args)
                {
                   ....
                },
                delegate(object sender, RunWorkerCompletedEventArgs args)
                    {
                       ...
                    }
            );
            uiProgress.Start();
like image 412
Niels Bosma Avatar asked Dec 07 '14 15:12

Niels Bosma


4 Answers

Your Start() method must be called from code that runs on the UI thread to allow the BackgroundWorker to operate correctly. It was not when you get this exception. Add protective code to your method so you can diagnose this mishap:

    public void Start()
    {
        if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) {
            throw new InvalidOperationException("Bug! Code called from a worker thread");
        }
        backgroundWorker.RunWorkerAsync();
    }

Now you can set a breakpoint on the throw statement and use the debugger's Call Stack window to find out why this happened.

like image 62
Hans Passant Avatar answered Oct 02 '22 05:10

Hans Passant


You are calling UI operation on background thread. This is the reason for that exception. I would use entirely different method to make the progress form the best one is to use Task with IProgress. The other way it to use this:

private void backgroundWorker_ProgressChanged( object sender , ProgressChangedEventArgs e )
    {

      this.UpdateOnMainThread(
        ( ) =>
        {
          wdgProgressBar.Value = e.ProgressPercentage;
          if ( this.Visible == false )
          {
            this.ShowDialog( );
            this.Update( );
          }
        } );
    }

    private void UpdateOnMainThread( Action action )
    {
      if ( this.InvokeRequired )
      {
        this.BeginInvoke( ( MethodInvoker ) action.Invoke);
      }
      else
      {
        action.Invoke( );
      }
    }

    private void backgroundWorker_RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e )
    {
      this.UpdateOnMainThread(
        ( ) =>
        {
          this.Hide( ); //Here I get a InvalidOperationException
          this.Dispose( );
        } );

    }
like image 38
Radin Gospodinov Avatar answered Oct 02 '22 05:10

Radin Gospodinov


Use the BeginInvoke() method on the form:

//http://msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.110).aspx

    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.BeginInvoke(new InvokeDelegate(InvokeMethod));               
    }

    public delegate void InvokeDelegate();

    public void InvokeMethod()
    {
        this.Hide(); 
        this.Dispose();
    }
like image 38
opadro Avatar answered Oct 02 '22 06:10

opadro


I think you can find some help here: BackgroundWorker hide form window upon completion . However don't forget to detach BackgroundWorker events and stop BackgroundWorker it self like explained in here: Proper way to Dispose of a BackGroundWorker . The problem can be in the

this.Dispose();

in the backgroundWorker_RunWorkerCompleted event. With that you are disposing form page. Is that what you want to do? Or you want to dispose BackgroundWorker? Disposing form page all resources are released so doing this.Hide(); a second time can be a mistake.

For more info, you can see this links: C# Form.Close vs Form.Dispose and Form.Dispose Method

like image 28
Ninita Avatar answered Oct 02 '22 05:10

Ninita