Thread Safety Made Easier Under Visual Studio 2005

Most of us know that controls in Windows forms are bound to a specific thread and are not thread safe, and thus UI controls created or referenced on non-UI thread must be marshaled back to the UI thread by using one of the Invoke methods.

In the article WinForms UI Thread Invokes: An In-Depth Review of Invoke/BeginInvoke/InvokeRequred on asp.net , Justin Rogers described in detail how to make cross thread usage of UI code in a thread-safe manner.

Threading problems are typically very hard to debug without the clear understanding of the problem domain. And the thread safety of UI components is vastly ignored by many developers. If you want to have an in-depth look of how the UI thread should be used please take a look at Justin’s article. What I wanted to show you is that the enhanced debugger in Visual Studio 2005 actually can detect some invalid cross-thread operations.

Consider the following code snippet for a windows form application:

private void BtnTest_Click(object sender, EventArgs e) {

TimerCallback timerCallBackDelegate =

new TimerCallback(TimerCallBackHandler);

System.Threading.Timer timer =

new System.Threading.Timer(timerCallBackDelegate, null, 100, 100);

}

private void TimerCallBackHandler(object state) {

i += 1;

Label1.Text = i.ToString();

}

The button click event creates a timer and on the timer callback a label is updated. If you run this code in Visual Studio 2003, it would appear that everything runs fine. But there is a potential bug in TimerCallBackHandler, namely updating the Label control on a non-UI thread (spawned by the timer).

If you run the above code in Vistual Studio 2005, you will get the following message:

InvalidOperationexception was unhandled

Cross-thread operation not valid: Control ‘Label1’ accessed from a thread other than the tread it was created on.

To fix this problem we need to replace the TimerCallBackHandler with the code below:

private void TimerCallBackHandler(object state) {

if (Label1.InvokeRequired) {

TimerCallback callBackDelegage =

new TimerCallback(TimerCallBackHandler);

BeginInvoke(callBackDelegage, new object[] { state });

} else {

i += 1;

Label1.Text = i.ToString();

}

}

Where the call to update Label1 is marshalled back to the UI thread if Invoke is required.

Clearly, such coding errors will be caught more easily with the new Visual Studio 2005 IDE. Note that this is a feature of the new development environment not the new version of the CLR. If you compile and run the first code snippet using 2.0 framework and run it without starting from the IDE, you will not get the exception message either.

As a side note, it seems that the internal error reporting mechanism has changed between framework 1.1 and 2.0. If we change the TimerCallBackHandler to the snippet below (note, here we create the label control on a thread other than the UI thread):

private void TimerCallBackHandler(object state) {

Label l = new Label();

l.Left = 10;

l.Top = 10;

l.Text = “Test “;

this.Controls.Add(l);

}

We would get the following exception message in Visual Studio 2003:

An unhandled exception of type ‘System.ArgumentException’ occurred in system.windows.forms.dll

Additional information: Controls created on one thread cannot be parented to a control on a different thread.

But if we run the same code in Visual Studio 2005: the following exception message appears:

InvalidOperationexception was unhandled

Cross-thread operation not valid: Control ‘Label1’ accessed from a thread other than the tread it was created on.

Which essentially is the same as the message from the first exception.

Be Sociable, Share!

3 Comments

  1. Pankaj Kohre says:

    Hi Kerry,
    I am stuck with the same problem as you mentioned in your post and that is i am calling a function form a buttons click.

    private void TimerCallBackHandler(object state) {

    Label l = new Label();

    l.Left = 10;

    l.Top = 10;

    l.Text = “Test “;

    this.Controls.Add(l);

    }

    and i am getting the same error

    InvalidOperationexception was unhandled

    Cross-thread operation not valid: Control ‘Label1’ accessed from a thread other than the tread it was created on.

    Plese help me how can i solve this problem.
    Thanks in advance…..[:)]

  2. kwong says:

    Hi Pankaj,

    You were trying to modify a UI component (a label) from a thread other than the UI thread. To fix the problem, you can use if (Label1.InvokeRequired) in the code snippet earlier in the post to marshall the call back to the UI thread. I hope this helps.

  3. John McPhrson says:

    Good article, works for me!

    Thanks…

Leave a Reply