Jul 10, 2008

Cross Threading

If you have one thread that tries to manipulate an object owned by another thread you are involved in cross threading. This can easily make your program go unstable. There is a common way this can creep into a program. If you have some task that takes a while to complete and you don't want your UI to become blocked, you naturally spin it off in a separate thread. If that task wants  to display progress updates on how that task is going either with a text box or a progress bar or some other control you can get cross threading errors.

There are two ways around this. This first is pretty well documented and I'll explain it just for reference but the second approach is the one I really want to talk about. (Update: now there are three as the BackgroundWorker  class can now handle a lot of this for you – also see my Cross Threading Revisited post)

Let's say you have a thread running that can detect an error and this thread raises an event so that your GUI knows about it. If your event handler in your form just goes and updates a text area you are engaging in cross threaded communication. Instead you want your event handler to call a method that checks for possible cross-threading and then use the control's Invoke method to execute the event handler in the same thread that owns the control. That looks something like this:

//create a delegate that you can use to call back with in the same thread
delegate void SetTextCallback(string text);

private void SetMemoText(string errMsg)
{
    // InvokeRequired required compares the thread ID of the
    // currentthread to the thread ID that owns the control.
    //memoErrorMessages in this case is a rich text box control on the form.
    // If these threads are different, it returns true.
    if (this.memoErrorMessages.InvokeRequired)
    {
        //set up the delegate to call back to this method again
        SetTextCallback set_text_delegate = new SetTextCallback(SetMemoText);
        //Use the control's Invoke method
        // to call the delegate. This will cause this method to get called again in the same thread.
        memoErrorMessages.Invoke(set_text_delegate, new object[] { errMsg });
    }
    else
    {
        //We end up here when we are called from the control's invoke method.
        memoErrorMessages.AppendText(errMsg);
    }
}

harder to find out about. Let me set the stage. I was programming a camera that captures images in a separate thread. This thread can kick off several events including one that tells me the image has been grabbed or that an error has occurred. I need to take that image and update and pictureBox.Image area or show the errors in a text area. Now I could use the above technique but I was actually writing a .DLL in C# for this camera to make it easier to work with. That means in my documentation I would have to explain that the images were grabbed in a separate thread and that the user of my .DLL should avoid cross threading etc. Now this is a lot to ask of a user of a .DLL and just asking for trouble. Instead I wanted the events to raise themselves in the same thread as the listener for the event. That way, the programmer using my .DLL never even needs to know the .DLL uses a separate thread. To keep things consistent here's an example of how I handled the error messages that were raised, the image processing was very similar it just had a more elaborate EventArgs class (updated the example in 2011 to use my new idiom for events and to leverage LINQ for the foreach).

public event EventHandler<ValueMessageEventArgs<string>> ErrorDetected = (sender, eventArgs) => { };
private void RaiseError(string errorMessage)
{
    ErrorDetected.GetInvocationList().ToList().ForEach(x => InvokeHandlerOnProperThread((EventHandler)x,errorMessage));
}

private void InvokeHandlerOnProperThread(EventHandler theHandler, string errorMessage)
{
    ISynchronizeInvoke sync_invoke = theHandler.Target as ISynchronizeInvoke;
    if (sync_invoke == null) return;
    ValueMessageEventArgs<string> event_args = new ValueMessageEventArgs<string>(errorMessage);

    if (sync_invoke.InvokeRequired)
    {
        //This will invoke in the thread of the listener and next time through we will            
        //end up  in the else clause because InvokeRequired will return false.                
        sync_invoke.Invoke(theHandler, new object[] { this, event_args });
    }
    else
    { theHandler(this, event_args); }
}
Unfortunately, I forget who had this original idea so I can't give credit where it's due. If you have a better way of doing this I would love to hear it.

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.