Saturday, October 16, 2004

Threadsafe GUI Programming

When I was working on Visual Chat back in 1998, I faced a severe performance problem when the chat client received data from the server, which caused some UI refreshing. Reason was that the TCP socket's receiving thread accessed Java AWT components directly, e.g. by setting a TextField's text, or by invoking Component.repaint().

I had not considered that while ordinary calls to Component.repaint() (within the main AWT event dispatch thread) are merged to one big paint effort once the thread runs idle, posting repaint events from another thread into the AWT event dispatch thread's (see below) queue causes constant repainting - a heavy performance issue. Once I figured out, it was easy to fix - I posted a custom event to the AWT thread's queue, which caused a call to Component.repaint() within the correct thread context, namely the AWT thread.

A connected issue is thread-safety. AWT Components or .NET Controls respectively their underlying native peers are not re-entrant, which makes it potentially dangerous to access them from any other thread than the one they were created on.

Once you think about it, it's quite obvious why thread-safety cannot be achieved like this: When one or more windows are being opened, the operating system associates them with the calling thread. Messages dedicated to the window will be posted to the thread's message queue. The thread then usually enters a message loop, where it receives user input, repainting and other messages dedicated to its windows. The message loop usually runs until the main window closes (signaled by the arrival of a WM_CLOSE message in case of Microsoft Windows).

The message loop implementation dispatches the messages to so called window functions, specific to each window class. A typical message loop implementation looks like this under Microsoft Windows:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


Modal dialogs are based on "inner" message loops that lie on the current call stack, and exit once the modal window closes.

Java AWT hides this mechanism from the programmer. As soon as a Java window is being opened, the framework creates the AWT event dispatch thread, which gets associated with the window by the underlying operating system, and runs the message loop for this and all other Java windows. The main thread then blocks until the AWT event dispatch thread exits. The AWT event dispatch thread is also reponsible for repainting. It collects all GUI refresh events, checks which regions have been invalidated, and triggers painting them.

Under .NET WinForms, Application.Run() starts the message loop on the current thread.

So when someone calls any AWT-Component-method (or MFC-method resp. WinForms-method, if you prefer) from another thread, this bypasses the thread that actually runs the message loop. Most of those methods are supposed to be called within the thread that holds the message loop, e.g. because member variables are not protected by synchronization from concurrent access. So you actually run the risk of destroying your Component's data.

How can we spawn a worker thread then, which runs in parallel, hence keeps the GUI responsive, but can display some visual feedback, e.g. a dialog containing a progressbar and a cancel button? Yes we may always give Application.DoEvents() a try, but we might also want to invoke a Stored Procedure or a Webservice, which blocks program flow for several seconds. Then we need some kind of inter-thread communication, posting events to the message loop for example. The registered Window Function/EventHandler can then update the progressbar state or react on a button click. Its code will run inside the expected thread context.

Some years later I had to maintain somebody's old Java code, which just happened to behave in the same flawed way as described above. In the meantime, Swing offered some convenience methods for invoking funtionality inside the AWT event dispatch thread which I was happy to apply, namely SwingUtilities.invokeLater() and SwingUtilities.invokeAndWait(). The .NET WinForms API provides Control.Invoke() and Control.BeginInvoke(). The .NET WinForms library will actually throw an exception if any Control members are being accessed from another thread than the one that created it, hence runs the message loop.