1
//button is clicked
//worker starts

private void processWorker_DoWork(object sender, DoWorkEventArgs e)
{
     string code = DoLongWorkAndReturnCode();

     if (code != 0)
     {
         MessageBox.Show("Error!");
         EnableAllButtons(); // this is defined in the other thread and it's where i run into the error.
     }
     else
     {
          string code = DoAnotherLongProcessAndReturnCode(); 
          if (code != 0)
          {
               MessageBox.Show("Error 2!");
               EnableAllButtons(); // again, this is defined in the other thread
          }
     }
}

I'm running into a cross threading error because "EnableAllButtons()" is defined in a different thread.

How do I go about enabling all buttons in one thread, from a different thread?

JJ.
  • 9,580
  • 37
  • 116
  • 189
  • 1
    Clearly you want to call this in your RunWorkerCompleted event handler. – Hans Passant Aug 27 '12 at 21:32
  • 1
    possible duplicate of [Cross thread operation not valid in c#](http://stackoverflow.com/questions/1523878/cross-thread-operation-not-valid-in-c-sharp) – shf301 Aug 27 '12 at 21:32
  • @HansPassant, right. And I want to do this the correct way, but how do I go about "passing" the correct error message to RunWorkerCompleted? Each method returns a code and I display an error message that is related to it. So I'm trying to be specific here. – JJ. Aug 27 '12 at 21:42
  • @Testifier RunWorkerCompleted allows you to access the result of DoWork - this result could be your code. If the code isn't want you expect then you can spin off the other DoAnotherLongProcessAndReturn code in a background worker (and check the result of that!) => e.Result = code; (which you can then check in RunWorkerCompleted) – dash Aug 27 '12 at 21:44
  • Throw an exception. You'll get it back in the event handler from e.Error – Hans Passant Aug 27 '12 at 21:48

3 Answers3

2

Based on your names I am assuming you are using a BackgroundWorker. You must either update your controls in ProgressChanged, RunWorkerCompleted, Control.Invoke or Control.BeginInvoke (You do not need to fire EndInvoke for Control's BeginInvoke).

My recommendation is throw a exception in your background worker (if you are not using code other than checking for 0, if not use the Result method.) and check the Error property of the parameter passed in to the RunWorkerCompleted event. From there you can spin up another background worker or stop. you will be running on the main thread so you can change your buttons without invoking.


Example: Enabling the buttons via BeginInvoke

With this example you can use your code as is just modify EnableAllButtons

private void EnableAllButtons() 
{ 
    //See if the main form needs invoke required, assuming the buttons are on the same thread as the form
    if(this.InvokeRequired) 
    {
        //Calls EnableAllButtons a seccond time but this time on the main thread.
        //This does not block, it is "fire and forget"
        this.BeginInvoke(new Action(EnableAllButtons));
    }
    else
    {
        btnProcessImages.Enabled = true; 
        btnBrowse.Enabled = true; 
        btnUpload.Enabled = true; 
        btnExit.Enabled = true; 
        ControlBox = true;
    }
}

Example: Returning data via Result

private void processWorker1_DoWork(object sender, DoWorkEventArgs e)
{
     string code = DoLongWorkAndReturnCode();
     e.Result = code;
}
private void processWorkers_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (code != 0)
    {
        MessageBox.Show("Error!");
        EnableAllButtons(); // this is defined in the other thread and it's where i run into the error.
    }
    else
    {
          doAnotherLongProcessAndReturnCodesBackgroundWorker.RunWorkerAsync(); 
    }
}

Example: Returning data via Exception + reusing event handlers.

private void processWorker1_DoWork(object sender, DoWorkEventArgs e)
{
     string code = DoLongWorkAndReturnCode();
     if (code != 0)
     {
         thow new MyCustomExecption(code);
     }
}

private void processWorker2_DoWork(object sender, DoWorkEventArgs e)
{
     string code = DoAnotherLongProcessAndReturnCode(); 
     if (code != 0)
     {
         thow new MyCustomExecption(code);
     }
}

private void processWorkers_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if(e.Error != null)
    {
        string message;

        //See if it was our error
        if(e.Error.GetType() == typeOf(MyCustomExecption))
        {

            //Choose which error message to send
            if(sender.Equals(processWorker2))
                message = "Error2!";
            else
                message = "Error!";
        }
        else
        {
            //Handle other types of thrown exceptions other than the ones we sent
            message = e.Error.ToString();
        }


        //no matter what, show a dialog box and enable all buttons.
        MessageBox.Show(message);
        EnableAllButtons();
    }
    else
    {
        //Worker completed successfully. 
        //If this was called from processWorker1 call processWorker2

        //Here is the code that was returned from the function
        if(sender.Equals(processWorker1))
            processWorker2.RunWorkerAsync();
    }

}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
0

In order to access a control in another thread, you need to use the Control.Invoke as described here: Control.Invoke Method (Delegate)

Roland
  • 972
  • 7
  • 15
0

If you must access a UI control directly from another thread, check out this most useful extension method

https://stackoverflow.com/a/3588137/141172

BackgroundWorker provides an event for progress updates. If your changes to the UI are of that nature, consider using that event.

Community
  • 1
  • 1
Eric J.
  • 147,927
  • 63
  • 340
  • 553