views:

1993

answers:

11

I have a winforms application, the issue has to do with threading. Since I am calling 'MyCustomCode() which creates a new thread, and calls the method 'SomeMethod()' which then accesses MessageBox.Show(...).

The problem has to do with threading, since the newly created thread is trying to access a control that was created on another thread.

I am getting the error:

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

public TestForm()
{
 InitializeComponent();


 // custom code
 //
 MyCustomCode();


}

public void SomeMethod()
{

 // ***** This causes an error  ****

 MessageBox.Show(this, 
  ex.Message, 
  "Error", 
  MessageBoxButtons.OK, 
  MessageBoxIcon.Error
 );
}



private void InitializeAutoUpdater()
{
     // Seperate thread is spun to keep polling for updates
     ThreadStart ts = new ThreadStart(SomeMethod);
     pollThread = new Thread(ts);
     pollThread.Start();
}

Update

If you look at this example http://www.codeproject.com/KB/cs/vanillaupdaterblock.aspx, the method CheckAndUpdate is calling MessageBox.Show(..) that is what my problem is. I would have thought that code was good to go!

Funny thing is that this code was working just fine on Friday???

+4  A: 

You cannot acces UI elements from multiple threads.

One way to solve this is to call the Invoke method of a control with a delegate to the function wich use the UI elements (like the message box). Somethin like:

public delegate void InvokeDelegate();

public void SomeMethod()
{

    button1.Invoke((InvokeDelegate)doUIStuff);


}


void doUIStuff()
{
           MessageBox.Show(this,   
                ex.Message, 
                "Error", 
                MessageBoxButtons.OK, 
                MessageBoxIcon.Error
            );
}
AlbertEin
You should check if invoke is required, e.g. button1.InvokeRequired.
RickL
And if the handle of the form is not yet created, InvokeRequired always return false. That is why SynchronizationContext could be more recommended.
Romain Verdier
A: 

You should NOT use BeginInvoke, you should use Invoke, then once you grasp that, you can look into using BeginInvoke if really needed.

leppie
+2  A: 

OR

Romain Verdier
+1  A: 

You should check this MSDN article.

pmlarocque
A: 
    '*******************************************************************
    ' Get a new processor and fire it off on a new thread.
    '*******************************************************************
    fpProc = New Processor(confTable, paramFile, keyCount)
'
    AddHandler fpProc.LogEntry, AddressOf LogEntry_Handler
'    
    Dim myThread As System.Threading.Thread = New System.Threading.Thread(AddressOf fpProc.ProcessEntry)
'      
    myThread.Start()
'
'
'   
    Then in the parent app you have:

       '*************************************************************************
       '     Sub: LogEntry_Handler()
       '  Author: Ron Savage
       '    Date: 08/29/2007
       '
       ' This routine handles the LogEntry events raised by the Processor class
       ' running in a thread.
       '*************************************************************************
       Private Sub LogEntry_Handler(ByVal logLevel As Integer, ByVal logMsg As String) Handles fProc.LogEntry
        writeLogMessage(logMsg);
       End Sub

That's what I do.

Ron

Ron Savage
WTF? What is this?
leppie
It uses the event message queue to handle inter process communications (thread to parent in this case. :-) I have an "unknown number" of threads all sending updates to the same parent window.
Ron Savage
I'm in agreement with leppie.
RickL
+5  A: 

to avoid cross-thread exceptions (InvalidOperationException), here is the code pattern:

protected delegate void someGuiFunctionDelegate(int iParam);

protected void someGuiFunction(int iParam)
{
    if (this.InvokeRequired)
    {
     someGuiFunctionDelegate dlg = new 
            someGuiFunctionDelegate(this.someGuiFunction);
     this.Invoke(dlg, new object[] { iParam });
     return;
    }

    //do something with the GUI control here
}

i agree that this is annoying, but it is an artifact of the fact that windows GUI controls are not thread-safe. The exception can be turned off with a flag somewhere or other, but don't do that as it can lead to extremely hard to find bugs.

Steven A. Lowe
A: 

The number one rule when multi-threading is you absolutely cannot touch the UI from worker threads. There are many ways to implement multi-threading and it's very hard to get it "right".

Here's a succinct article that should help you out - Updating the UI from a Secondary Thread

And here's a lengthy article that discusses threading in-depth - Multi-threading in .NET

whatknott
+2  A: 

To keep things simple you can look into using the BackGroundWorker class. This class will provide a framework for to handle your threading and progress notification events. Your ui thread will handle the progress event and display the error message you pass back.

Aaron Fischer
A: 

Check for InvokeRequired

A: 

I praticularly like a recursive call.

public delegate void InvokeDelegate(string errMessage); 

    public void SomeMethod() 
    { 
        doUIStuff("my error message");
    } 


    void doUIStuff(string errMessage) 
    { 
        if (button1.InvokeRequired)
            button1.Invoke((InvokeDelegate)doUIStuff(errMessage)); 
        else
        {
               MessageBox.Show(this,    
                    ex.Message,  
                    errMessage,  
                    MessageBoxButtons.OK,  
                    MessageBoxIcon.Error 
                ); 
        }
    } 
Joel Barsotti
A: 

I know this is an older post, but I recently found an elegant solution to this problem using generics and extension methods. This is a combination of the authors works and some comments.

A Generic Method for Cross-thread Winforms Access

http://www.codeproject.com/KB/cs/GenericCrossThread.aspx

public static void Manipulate<T>(this T control, Action<T> action) where T : Control
{
    if (control.InvokeRequired)
    {
        control.Invoke(new Action<T, Action<T>>(Manipulate),
                    new object[] { control, action });
    }
    else
    { action(control); }
}

This can be called in the following manner, for simplicity I used a label.

someLabel.Manipulate(lbl => lbl.Text = "Something");
Arical