Making Controls Thread-safely

Introduction

If you use multithreading to improve the performance of your Windows Forms applications, you must make sure that you make calls to your controls in a thread-safe manner. Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.

Using the Code

Let's suppose that we have a WinForm with two textboxes and four buttons. Each button starts a new thread that executes some method: UnsafeMethod(), SafeMethod(), SafeMethod2() or SafeMethod3().

UnsafeMethod() will fail with a cross-thread exception because the .NET Framework does not allow to access a control created in another thread. To avoid such a situation, we need to check Control.InvokeRequired property and if it is true when using Control.Invoke() method and so on...

SafeInvokeUtils does exactly the same things for you and a bit more, because now you do not need to check for invoke requirements, just use GetPropertyValue() or SetPropertyValue() for control's properties and InvokeMethod() for control's methods (See: SafeMethod(), SafeMethod2() and SafeMethod3()).

I generally prefer to avoid using Invoke on controls when possible, since it will make the performance of the non-UI thread highly dependent upon that of the UI thread. If some function on the UI thread takes 100ms to execute, the Control.Invoke will block until that has happened.

Obviously there are some types of controls for which precise sequencing is mandatory and there isn't any real alternative to using Control.Invoke. Generally, though, I prefer to use an integer to keep track of whether an update is pending; when I change information related to the control, I Interlocked.Exchange the integer with '1'; if it was zero, I call Control.BeginInvoke. The first step of the control's update routine is to set the update flag to zero. If an attempt is made to change the control's data after the update has begun, the update will be performed again (to allow for the fact that the control may or may not have been updated with new data). If many updates to the control are attempted before the UI thread gets around to drawing, the earlier updates will essentially be skipped. Thus, something like a progress bar may be updated as many times as desired without overly dogging performance.