Wednesday, December 15, 2010

C# GUI Hangs on Invoke

If you've ever tried doing any advanced work using GUIs in C#, you'll be friends/enemies with the good old fashioned Cross-thread operation not valid exceptions. GUI controls run on their own thread and any other thread can't access them.

The great work around is to invoke the control. For instance, I use the following method to update a richTextBox control from any thread I happen to be in:

public delegate void StringParameterDelegate(string value);

public void UpdateRichTextBoxStatus(string value)

{

if (InvokeRequired)

{

// We're not in the UI thread, so we need to call Invoke

Invoke(new StringParameterDelegate(UpdateRichTextBoxStatus), new object[] { value });

return;

}

// Must be on the UI thread if we've got this far

richTextBoxStatus.Text = value + richTextBoxStatus.Text;

}



It checks to see if the control needs invoking (to avoid the cross-thread exception). If it does, it will reursively call itself again using a delegate and then update the richTextBox.

This worked fine and dandy most of the time. However, I had one condition where different invocations were happening at the same time, on different controls, but being invoked from an external, managed DLL. What happened is the invoke occured, but then the whole program hung on a call to the DLL.

Further investigation (god bless the parallel stacks threading debugging view) showed that one of the invocations went into a sleep state, waiting for the other invocation to finish. The Invoke call itself is synchronous, so the next invoke call was waiting for the original to finish. And hence the program just hung there. Waiting... waiting... never finishing.

Well there's an easy workaround for this. Instead of calling Invoke, call BeginInvoke! This makes the call asynchronous. The function thus becomes:

public delegate void StringParameterDelegate(string value);

public void UpdateRichTextBoxStatus(string value)

{

if (InvokeRequired)

{

// We're not in the UI thread, so we need to call BeginInvoke

BeginInvoke(new StringParameterDelegate(UpdateRichTextBoxStatus), new object[] { value });

return;

}

// Must be on the UI thread if we've got this far

richTextBoxStatus.Text = value + richTextBoxStatus.Text;

}

1 comment: