How to avoid exceptions upon exiting application

Posted on | December 23, 2011 | No Comments

Almost every modern application is multi-threaded. Threads are needed to work with various devices and IP protocol stack and to perform demanding computing operations. Results of work performed in these threads should be displayed in graphical controls. Control.Invoke / Control.BeginInvoke are the main methods used for thread synchronization.  These methods work excellent only when control is initialized and contains non-zero wondow handle.

For synchronization programmers often use a Form object that contains controls requiring content update. They probably do it because it’s so easy to implement. It’s so easy to call the following code:

myForm.BeginInvoke(new MethodInvoker(()=>
{
    //a code to be executed in the GUI thread
}), null);

However, simple doesn’t mean correct! GUI thread is not a separate control property but something that is used by all controls. GUI thread lifetime exceeds lifetime of an individual control. Controls and forms can be created or deleted, and all these operations are done via message loop common for all controls and forms. Now let’s say that we started synchronization via myForm.BeginInvoke() upon notification from non-GUI thread. This call adds a window message to message loop. When this message is processed, window handle associated with it is dispatched to specified control or form, and after that a delegate is called. So, what if the form has been closed before that? An exception will be thrown during dispatching. Don’t think that this situation is unusual. When application terminates, it closes windows and performs various actions with end devices, IP stack, etc. These devices in turn may notify GUI of their termination followed by synchronization. This may cause frequent exceptions that are hard to understand and even harder to fix.

We are absolutely convinced that there is only one way out – to create a global dispatcher that is initialized when application starts and is closed when it stops. Dispatcher may be based on the main form launched in Application.Run(). All multi-threaded synchronization should be performed only via this dispatcher. Besides, this approach enables use of other dispatcher for unit tests. As an example we will demonstrate this idea with a code:

 

//Dispatcher class
static class Dispatcher
{
    private static ISynchronizeInvoke _invoker;

    internal static void Init(ISynchronizeInvoke invoker) {_invoker = invoker;}

    public static ISynchronizeInvoke GuiDispatcher { get { return _invoker; }}
}

//How to initialize
using (Form mainForm = new Form())
{
    Dispatcher.Init(mainForm);

    //other initialization code...

    //Start the main loop as usual
    Application.Run(mainForm);
}

//How to use:
Dispatcher.GuiDispatcher.BeginInvoke(new MethodInvoker(()=>
{
    //some code to be executed in the GUI thread
}), null);

 
 

Comments

Leave a Reply