WPF The calling thread must be STA

2011-10-04


In a multithread WPF project, we got program crash, but actually WPF did not give us any help or error information on the screen, then we tried to debug using steps, finally we got the following error information on a line of code with adding related variable in this line of code into Watch window:

The calling thread must be STA, because many UI components require this.

our code was:

 for (int i = 0; i < Commons.MaxDeviceCount; i++)
 {
     if ((iAddress >= DeviceIODataList.DeviceList[i].StartAddress)
         && (iAddress <= DeviceIODataList.DeviceList[i].EndAddress))
     {
         retDeviceInd = i;
         retV = true;
         break;
     }
 }

We double checked the code and found the reason must caused by child thread try to load a variable which created in main thread.

DeviceIODataList.DeviceList was created in another thread. that’s why we got error.

Then we searched help information, and got the very helpful information from Microsoft support team as following:

How to access WPF controls from another thread? (BY Marco Zhou)

Like many UI frameworks such as Windows Forms, WPF also imposes a single threading model, which means that you can only access a specified DispatcherObject derivative from the thread which creates it. In Windows Forms, each Control will implement ISynchronizeInvoke interface, this interface exposes a set of methods such as Invoke and BeginInvoke to impose a common thread synchronization contract which we could use to access a control from another thread. In WPF, we also have such type of thing, but those operations are wrapped up in a class called Dispatcher, Dispatcher is WPF way of enabling this type of thread synchronization model.

The following is an example of how to modify the TextBox.Text property when the caller is in a different thread:

_// Resets textbox text from another thread

textBox.Dispatcher.Invoke(DispatcherPriority.Background, new Action(()=>
{
  textBox.Text = "New text";
}));

So our code changed to the following and works :

for (int i = 0; i < Commons.MaxDeviceCount; i++)
 {
     int tempStartAddr = -1;
     int tempEndAddr = -1;

     DeviceIODataList.DeviceList[i].Dispatcher.Invoke(new Action(() =>
     {
         tempStartAddr = DeviceIODataList.DeviceList[i].StartAddress;
         tempEndAddr = DeviceIODataList.DeviceList[i].EndAddress;

     }));

     if ((iAddress >= tempStartAddr)
         && (iAddress <= tempEndAddr))
     {
         retDeviceInd = i;
         retV = true;
         break;
     }
 }

More detail about how threads work in C# 4.0, please read Joseph Albahari’s Threading in C#.