Mysterious Hang or The Great Deception of InvokeRequired

Summary

Windows Forms application that worked fine under .NET 1.1 may start hanging under .NET 2.0 when user preferences are changed. "User preferences" cover large variety of things from double-click speed to menu fade effects: see documentation for WM_SETTINGCHANGE and SystemParametersInfo for more details.

The hang does not happen under .NET 4.0 or .NET 4.5, due to modifications in system libraries. The hang also does not affect WPF applications. WPF typically does not create a window handle for each control, and unlike Windows Forms WPF uses System.Windows.Threading.Dispatcher for thread synchronization.

If you encountered the bug and are just interested in fixing it ASAP, jump directly to the solution.

The original article was written in 2008 and probably is of mostly historic interest by now.

Sample Application

Sample application that demonstrates the mysterious hanging bug: MysteriousHang.zip (32K).

The archive contains two projects:

  • MysteriousHang: a Windows Forms application prone to the mysterious hang.
  • Freezer: an application that induces the hang by broadcasting WM_SEETINGCHANGE message to all top-level windows in the system.

MysteriousHang sample is centered around the following piece of code:

private void WorkerThreadFunc()
{
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(WorkerThreadFunc), null);
        return;
    }
    IntPtr handle = new Form().Handle;
}

This code is executed on a non-GUI thread and leads to the mysterious hanging bug under the right circumstances.

Discovery of the bug

On some not so lucky day several months ago I received a couple of user complaints that my Windows Forms application mysteriously hangs every now and then. This proved to be quite hard to reproduce. The hang occured infrequently and tended to happen when the user walks away from the computer. Even worse, the hang never occured under debugger. Once hung, the application window would freeze completely: it was not even possible to move it.

The version of the application with the hang was running under .NET 2.0. Interestingly, the older, .NET 1.1 version of the same application never hung.

Once I was able to attach the debugger to the hung application, I found that the main GUI thread is stuck inside SystemEvents.OnUserPreferenceChanged() method waiting forever on some strange event. This was a big hint that allowed me to reproduce the problem almost at will. OnUserPreferenceChanged() is called in response to the WM_SETTINGCHANGE message. The application hangs when the user changes background bitmap, or does something else that causes broadcast of this message to all top-level Windows in the system.

The Cause

It's Nobody's Fault, Really

After several days of googling, intriguing debugging sessions, and examining tons of Windows Forms code via .NET reflector I discovered the chain of events that leads to the hang. It is definitely not a simple bug. It is a bizzare set of relatively innocent circumstances, that, when combined, lead to the deadly outcome. This is what makes this problem interesting.

I proceed by describing the pieces of the puzzle one by one, and then you will see how they work together. The contributors to the problem are:

Due to all of the above, GUI controls may be created on a worker thread that has a wrong synchronization context. If these controls subscribe to the UserPreferenceChanged system event, it leads to the blocking of the main GUI thread when a user preference change occurs.

Deferred Creation of Win32 Windows Handles

In an attempt to improve performance, .NET defers creation of real Win32 windows as much as possible. Thus, if I simply do something like this:

Control ctrl = new Control();

it does not cause creation of a Win32 window. The window handle will be created only when the control needs to be actualy shown on screen, or when the programmer explicitly requests the control's handle via the Handle property.

ctrl.Show(); // creates handle if control actually becomes visible
IntPtr handle = ctrl.Handle; // creates handle unconditionally

InvokeRequired May Lie

Here is a typical code snippet for marshalling notifications to the GUI thread:

delegate void MyHandlerDelegate();
        
void MyHandler()
{
    // "The BeginInvoke dance"
    if (this.InvokeRequired) // assuming this descends from Control
    {
        BeginInvoke( new MyHandlerDelegate(MyHandler) );
        return;
    }

    // assume we are on the main GUI thread
    ... do GUI stuff ...
}

This code works fine most of the time, but not all the time. How does Control.InvokeRequired know whether invoke is required? One can find the answer by looking at reflector. Here is how (simplified) code for InvokeRequired looks like:

public bool InvokeRequired
{
    get
    {
        HandleRef hWnd;
        int lpdwProcessId;
        if (this.IsHandleCreated)
        {
            hWnd = new HandleRef(this, this.Handle);
        }
        else
        {
            Control wrapper = this.FindMarshallingControl();
            if (!wrapper.IsHandleCreated)
            {
                return false; // <==========
            }
            hWnd = new HandleRef(wrapper, wrapper.Handle);
        }
        int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(hWnd, out lpdwProcessId);
        int currentThreadId = SafeNativeMethods.GetCurrentThreadId();
        return (windowThreadProcessId != currentThreadId);
    }
}

The code first locates a suitable Win32 window handle. If current control does not have a handle, FindMarshallingControl() will walk up through the control's parents until it either finds a parent with a valid handle, or reaches the end of the hierarchy. Each Win32 handle has been created on some thread, and Windows keeps track of this information. The code obtains the ID of the marshalling handle's thread. If it is the same as the executing thread, invoke is not required. If it is different from the executing thread, invoke is required. An interesting thing happens if a suitable window handle cannot be found (marked by the red arrow above).

When neither the control nor any of its parents have a valid window handle, InvokeRequired returns false unconditionally.

In fact, it has no choice. There is no information about the control's window thread. When (and if) the control's Win32 handle is created on some thread, that thread will become control's window thread. But this had not happened yet. If InvokeRequired returned true, user probably will do BeginInvoke() or Invoke() on the control, which will cause an exception. Thus, returning false seems like the only reasonable choice under the circumstances.

This will obviously wreck havoc if a code doing the BeginInvoke dance executes on a non-GUI thread. The "if" condition will return false, and the "GUI stuff" will end up being executed on a non-GUI thread leading to potentially disastrous concequences.

In real life, a situation when InvokeRequired cannot find a window handle may occur in at least two scenarios:

  • Early in the program startup, e.g. before calling Application.Run()
  • Early in the life of the control, e.g. while executing the control's constructor

A control that belongs to a form is typically initialized as follows:

class MyForm : Form
{
    MyControlClass myControl;

    public MyForm()
    {
        InitializeComponent();
    }

    void InitializeComponent()
    {
        myControl = new MyControlClass();    
        // at this point myControl has no window handle and no parent
        this.Controls.Add(myControl);
    }
   
}

While the control's constructor is executed, the control has no window handle and no parent. If the constructor spawns an asynchronous handler that calls InvokeRequired from a non-GUI thread, there is a race condition. If the constructor exits and the control gets its parent before the handler calls InvokeRequired everything works fine. If, however, the constructor lingers on, and the handler calls InvokeRequired while the control is still an orphan, InvokeRequired will return false and bad things will happen.

Synchronization Contexts

.NET 2.0 introduces a new class System.Threading.SynchronizationContext. It is supposed to generalize thread synchronization mechanisms like Control.BeginInvoke. Each thread has a (lazy-initialized) synchronization context which is retrieved via SynchronizationContext.Current. The programmer can then do things like context.Post(someDelegate), which is supposed to be an asynchronous call, and context.Send(someDelegate), which supposed to be a synchronous, blocking call.

SynchronizationContext defines both the interface for synchronization contexts and the default implementation. In this default implementation Post() calls ThreadPool.QueueUserWorkItem() and Send() simply calls the parameter delegate.

Windows Forms define a special implementation of SynchronizationContext called WindowsFormsSynchronizationContext. In this implementation Post() calls BeginInvoke() on a special "marshalling control", and Send() calls Invoke() on the same control.

Windows Presentation Foundation and ASP.NET define their own implementations of SynchronizationContext.

If a thread does not specifically setup synchronization context and someone calls SynchronizationContext.Current, default implementation will be returned, something close to new SynchronizationContext(). However,

Creating any Windows Forms control on the thread automatically installs WindowsFormsSynchronizationContext

as current synchornization context. This is done in the constructor of the Control class.

Handling System Events

Microsoft.Win32.SystemEvents class defines a number of static events such as UserPreferenceChanged.

In .NET 1.1 these are regular C# events. Each event keeps a list of subscriber delegates. When specific event needs to be raised, subscriber delegates are directly called one by one. It is the subscriber's responsibility to implement any necessary thread synchronization.

.NET 2.0 tries to reduce the burden on the subscriber and builds certain thread synchronization into the system. Instead of keeping a plain list of subscriber delegates, in .NET 2.0 each event keeps list of delegates and corresponding thread contexts. In other words, when someone does

SystemEvents.UserPreferenceChanged += subscriberDelegate;

custom subscription code calls SycnhronizationContext.Current and adds a structure containing subscriberDelegate delegate and the synchronization context to the list of subscribers. When specific event needs to be raised, the system calls context.Send(subscriberDelegate) for each subscriber in order.

Keep in mind that this applies only to the events in the SystemEvents class, not to all events everywhere. SystemEvents class has custom add and remove handlers for its events that implement this behavior.

Automatic Subscription to System Events

Some Windows Forms controls automatically subscribe to SystemEvents.UserPreferenceChanged in their OnHandleCreated() method, or in other methods. This includes:

  • Form and all top-level controls
  • DataGridView
  • DateTimePicker
  • DomainUpDown
  • MonthCalendar
  • NumericUpDown
  • ProgressBar
  • RichTextBox

So, it is enough to execute code like this:

IntPtr handle = new Form().Handle;

or

new Form().Show();

to create subscription to SystemEvents.UserPreferenceChanged. Note, however, that it is not even necessary to create the form directly in your code. Someone else's code may do this for you. E.g., Infragistics library creates a hidden form that it uses for device context measurements. Thus, you may be doing an innocent looking Infragistics call, but it will create a Windows Form and subscribe to the UserPreferenceChanged event.

Putting It All Together

We have examined all pieces of the puzzle and are finally ready to see the whole picture. Here is the sequence of events that leads to the mysterious hang:

  1. A control is created on the GUI thread. Creation of the window handle is deferred.
  2. There is code on a worker thread that does the BeginInvoke dance.
  3. The worker thread code is called while neither the control nor any of its parents have window handles.
  4. InvokeRequired returns false on the worker thread.
  5. The worker thread proceedes with the GUI calls.
  6. One or more controls are created on the worker thread. This installs WindowsFormsSynchronizationContext on the thread.
  7. Some GUI calls lead, directly or indirectly, to subscription to the SystemEvents.UserPreferenceChanged event.
  8. SystemEvents class stores current synchronization context, which is WindowsFormsSynchronizationContext, along with the subscriber delegate.
  9. User preference change occurs, e.g. desktop background bitmap is changed.
  10. Windows procedure on the main GUI thread receives WM_SETTINGCHANGE message.
  11. The code in SystemEvents class raises UserPreferenceChanged event. It examines the list of subscribers and calls Send() on the stored thread context.
  12. WindowsFormsSynchronizationContext.Send() sends a message to the worker thread and blocks until the message is processed.
  13. Since the worker thread does not have a message loop, the message never gets processed.
  14. The main GUI thread is blocked forever. The application hangs.

What Happens under .NET 1.1

Under .NET 1.1 InvokeRequired behaves essentially in the same way. Thus, it may lie to you just like under .NET 2.0 and lead to execution of GUI code on a non-GUI thread.

From the other hand, .NET 1.1 does not have a concept of a thread synchronization context. The SystemEvents class stores plain delegates and calls them directly when a system event occurs. Therefore, while the behavior under .NET 1.1 is still dangerous, the mysterious hanging bug does not occur under .NET 1.1. However, if I take a .NET 1.1 program and run it under .NET 2.0 using <supportedRuntime> configuration attribute, the bug will occur.

What Happens under .NET 4.0 and later

Frankly, I am not sure. At some point I just discovered that the bug does not happen under .NET 4. InvokeRequired may still incorrectly return false, and creating an instance of Windows.Forms.Control still installs a Windows Forms synchronization context. Most likely Microsoft has changed the code of the SystemEvents class so that event invocation no longer waits for the the way user preference change no longer waits for the synchronization context.

Structure of The Sample Code

In order to demonstrate all possible combinations, I need two solutions: MysteriousHang.sln for VS 2003 and MysteriousHang_2_0.sln for VS 2005. The former solution includes Freezer and MysteriousHange projects, the latter includes only MysteriousHange project, since one version of freezer is enough for our needs.

.NET 2.0 binary files go to the bin_2_0 directory. Unfortunately, there is no way to change the obj directory, so it is shared between the two versions. If you get internal compiler errors when compiling under VS 2003, manually clean the obj directory.

Reproducing the Bug

The mysterious hanging bug occurs only under .NET 2.0 and when the process is not debugged using VSHost. Different situations are summarized in the table below:

CompilerRuns underVS HostHangs
VS 2003.NET 1.1nono
VS 2003.NET 2.0noyes
VS 2005.NET 2.0noyes
VS 2005.NET 2.0yesno

Notes:

  1. VS Host is not supported under VS 2003.
  2. Programs compiled with VS 2005 cannot run under .NET 1.1.

To reproduce the bug:

  1. Run MysteriousHang from the bin_2_0 folder. Make sure it says it will hang.
  2. Run Freezer.
  3. Press the "Freeze 'em! button
  4. Wait until the hourglass cursor goes away (this may take a while).
  5. MysteriousHang window is now frozen

To force .NET 1.1 application to run under .NET 2.0 uncomment the <supportedRuntime version="v2.0.50727"/> fragement in the application configuration file.

The Solution

There are several ways to avoid or at least detect the lying InvokeRequired. First of all, use early detection. If you believe that certain piece of code must execute only on the main GUI thread - verify that. The easiest way to do so is to give the main GUI thread a name and verify that the current thread has that name:

void Main()
{
    Thread.Current.Name = "MainGUI";
    ...
}

void EnsureMainGuiThread()
{
    if (Thread.Current.Name != "MainGUI")
    {
        throw new InvalidOperationException("This code must execute on the main GUI thread");
    }
}

void SomeMethod()
{
    if (InvokeRequired)
    {
        BeginInvoke(...);
        return;
    }

    EnsureMainGuiThread();
    ...
}

There are also some active steps you can take to prevent bad things from happening:

  1. Force creation of the main form's Win32 handle as early as possible by calling its Handle property. You want the handle to exist before you make your first call to InvokeRequired.
  2. Avoid calling InvokeRequired on child controls. Always use main window. If you don't want to depend on the main window class, pass main window instance as ISynchronizeInvoke interface.
  3. Be especially careful when you do something asynchronously from your control's constructor. When you are in the constructor, the control is not yet attached to its parent, so InvokeRequired may lie to you, even if the parent has valid Win32 handle.

See Also

  1. Related Kim Greenlee's blog entry
  2. Microsoft KB article 943139 "Windows Forms application freezes when system settings are changed"
  3. Aaron Lerch'es blog entry about a similar problem
  4. General hang debugging tips by Tess Ferrandez

There is a way to avoid automatically installing windows synchronization context when creating a control by setting WindowsFormsSynchronizationContext.AutoInstall property to false. This, as well as references #3 and #4 above, was brought to my attention by Bill Voltmer, and is greatly appreciated.

From Aaron Lerch'es article: "referencing a ToolStripMenuItem's DropDownItems property will create a new DropDown control under the covers automatically if it didn't already exist.". Two lessons here:

  • Controls may be created when you don't expect it.
  • Properties with side effects should be avoided: if you make them too smart, your API users may not appreciate it.

The safest way would probably be to completely avoid touching UI objects from non-UI threads, even if the code looks innocent: you never know what it may do behind the scenes.

Feedback

Questions? Comments? Feel free to
Leave feedback