Cross-thread exceptions on unshown windows forms

This post is a bit more technical than my usual fare, it describes a fix for a pretty poorly documented problem with threading in a WinForms application.
Most of the discussion about threading in WinForms centres around the use of InvokeRequired to detect when it is necessary to marshal a method call back onto the main UI thread from a worker thread. Typically this code will look like:

      
        void LoginCompletedEventHandler(object sender, EventArgs e)
        {
           if (this.InvokeRequired)
            {
                this.BeginInvoke(new MethodInvoker(delegate() { UpdateUIWithLoginDetails(); }));
            }
            else
            {
                UpdateUIWithLoginDetails();
            }
        }
      
   

The short summary for InvokeRequired on MSDN says that this property "Gets a value indicating whether the caller must call an invoke method when making method calls to the control because the caller is on a different thread than the one the control was created on". The emphasis is mine. I initially read this passage to mean that the "thread the control was created on" is the thread which calls the Control's constructor. I only found the correct interpretation much later - the important thread is the one that creates the Control's handle, not the one that creates the Control object.

This leaves plenty of room for difficult to track down cross-threading exceptions to creep in. As an example I present some simplified version of the problem I encountered.

The scenario was simple, the application should be able to start minimised to the system tray if the user had previously stored their login details etc. and be capable of being opened from there. The application would create the main Form but only show it if there were no stored login details, otherwise it would leave the form undisplayed and perform the login action in a background thread. A notification icon was put in the system tray and the user could click this to pull up the main form (which would then be Show()n if required).

The creation of the form and the two places it could be Show()n where all occurring on the main thread and I was baffled as to why I was getting cross thread exceptions from the Show() call when it was clearly being called on the 'creator thread'.

Unfortunately, as part of the login process on the worker thread I was doing some nasty PInvoke calls, albeit to get some beautiful functionality, one of which read the property mainForm.Handle which will create a window handle if the form does not already have one. Thus the worker thread was ending up as the UI thread for the Control and causing my woes. This was ultimately detected by use of the handy Control event HandleCreated, subscribe to this event and stick in a break point to easily find out where the handle is actually being created and in which thread.

The solution is simple but ugly, put a spurious read of the Handle property straight after the call to mainForm's constructor as executed in the main thread.