Why do I get a NotSupportedException when updating a TextBox?

Have you ever been working on an application and found that when the code tries to update the Text property of a TextBox control that you encounter a NotSupportedException?  Have you seen this with a Label, ListBox, Button or another control?  If you have, the application is attempting to update the control from a worker thread instead of the thread that created the control (typically the main application thread).  Today I would like to examine why this happens.

On version 1 of the .NET Compact Framework, applications which attempted to update their user interface from a worker thread would typically hang.  Having an application hang is an unpleasant user experience.

With the release of version 2, the .NET Compact Framework will throw a NotSupportedException in the scenarios that would have previously lead to an application hang.  While exceptions are still not the best experience, they are far better than an application hanging.  Since the .NET Compact Framework is specifically throwing in this scenario, the application developer will find the issue every time a control is updated from a worker thread and the developer can fix the code.

If your device has had the string resource assemblies installed, the Message property of the NotSupportedException will contain an explanation for why the exception was thrown.

Control.Invoke must be used to interact with controls created on a separate thread.

Let's take a look at a couple of examples of what I have been talking about. 

The first snippet illustrates a simple thread delegate that attempts to update a TextBox control's Text property directly.

private void TestThread() {     // update our status     this.textBox1.Text = "Thread running";     // run until told to stop     while(!this.stopNow)     {         // simulated processing         Thread.Sleep(1000);     }     // update our status     this.textBox1.Text = "Thread stopped"; }

The above example will cause a NotSupportedException to be thrown on the line:

this.textBox1.Text = "Thread running";
Note also that the last line in the method will also cause a NotSupportedException to be thrown.  Let's take a look at a fix. 

The first step to fix our snippet is to define a delegate that takes a String argument so that we can pass our status message.

private delegate void UpdateStatusDelegate(String message);
Next, we add an implementation of a method that matches our delegate's signature.

private void DoUpdate(String message){    if(this.InvokeRequired)    {        // we were called on a worker thread        // marshal the call to the user interface thread        this.Invoke(new UpdateStatusDelegate(DoUpdate),                     new object[] { message });        return;    }    // this code can only be reached    // by the user interface thread    this.textBox1.Text = message; }
And, finally, we modify our thread method to call DoUpdate and pass the desired status message.

private void TestThread(){     // update our status     DoUpdate("Thread running");    // run until told to stop    while(!this.stopNow)    {        // simulated processing        Thread.Sleep(1000);    }    // update our status    DoUpdate("Thread stopped");}
Now, let's take a look at how this works.  When the thread method (TestThread) calls DoUpdate, the first thing that happens is that the Form's InvokeRequired method is called.  InvokeRequired compares the current thread with the thread which created the Form.  Since the threads are different (we called DoUpdate on a worker thread), we enter the if block.

Next, DoUpdate calls the Form's Invoke method and passes itself as a new instance of our UpdateStatusDelegate with the message argument encapsulated in an Object array.  Invoke then calls the DoUpdate method on the thread which created the Form.  After the call to Invoke completes, DoUpdates returns.  This return step is necessary so that the remainder of the method (the TextBox update) does not get called by the worker thread.

When DoUpdate is called by Invoke, it again calls the Form's InvokeRequired method.  This time, the call was made on the thread which created the Form, we skip the if block and the TextBox is updated.

Please note that for the above to work on version 1 of the .NET Compact Framework, you will need to write and call your own custom InvokeRequired method. I talk about one implementation for InvokeRequired in my post on the Visual Studio .NET 2003 Web Crawler sample.

Hidden threads
Sometimes, and application can be multi-threaded without realizing it.  When using the asynchronous programming model, your application's callback methods are called on a worker thread created and managed by the .NET Compact Framework runtime.  It is important to keep this in mind when using asynchronous methods and be sure that your user interface is updated only by the UI thread.

When I'm using an asynchronous callback method, I also write a delegate that I Invoke as appropriate.  This ensures that my user interface updates occur in the correct context.

-- DK

[Edit: update snippet formatting]

This posting is provided "AS IS" with no warranties, and confers no rights.