Simple Solution to Illegal Cross-thread Calls in C#

If the thread call is “illegal” (i.e. the call affects controls that were not created in the thread it is being called from) then you need to create a delegate so that even if the decision / preparation for the change is not done in the control-creating thread, any resultant modification of them will be. Basically, this is a long winded way of saying you can’t (and shouldn’t) access controls on a form from a thread that didn’t create the control in the first place.

.Net version older than 2.0 (1.0 and 1.1) would actually allow this to happen, but cross-threaded operations should always be dealt with cautiously and properly. The technical solution to the problem is to create a delegate function, and pass the delegate function to the Invoke() method on the control, which allows code to execute as if it was being executed from the control’s parent thread, but if you need to do this a lot, all the extra functions and delegates can make the code unreadable, if a lot of it is going on. Fortunately there are 2 “cheats” to the problem. The first is to use the ThreadStart delegate (System.Threading namespace), since its already setup as a simple delegate with one parameter. The second, is to use anonymous delegates, which is something I have covered in the past.

if (label1.InvokeRequired) {
  label1.Invoke(
    new ThreadStart(delegate {
      label1.Text = "some text changed from some thread";
    }));
} else {
  label1.Text = "some text changed from the form's thread";
}

Now, the more curious and perceptive of you are asking, why not just use control.Invoke() everytime you change a value? Well, the truth is you could. But calling control.Invoke() has a great deal more overhead than not calling it. However, when you avoid calling control.Invoke() and access UI objects directly from the UI thread, you’re writing incorrect code that could cause stability problems in your application.

The problem is that a “window” object in the underlying Windowing API in Win32, as represented by the HWND handle, has thread-affinity. It must be directly accessed only from the thread that created it. If it’s not, the results are unpredictable and can cause subtle, intermittent bugs.

By all means skip control.Invoke() if you’ve only got one thread. However, if you need to affect a UI object from a worker thread, you absolutely must use control.Invoke() to transition back to the UI thread before making that call or you’re in for a world of pain.

Leave a Reply

Your email address will not be published. Required fields are marked *