How to Safely Use SWT’s Display asyncExec
Most user interface (UI) toolkits are single-threaded and SWT is no exception. This means that UI objects must be accessed exclusively from a single thread, the so-called UI thread. On the other hand, long running tasks should be executed in background threads in order to keep the UI responsive. This makes it necessary for the background threads to enqueue updates to be executed on the UI thread instead of accessing UI objects directly.
To schedule code for execution on the UI thread, SWT offers the Display asyncExec() and syncExec() methods.
Display asyncExec vs syncExec
While both methods enqueue the argument for execution on the UI thread, they differ in what they do afterwards (or don’t). As the name suggests, asyncExec() works asynchronously. It returns right after the runnable was enqueued and does not wait for its execution. Whereas syncExec() is blocking and thus does wait until the code has been executed.
As a rule of thumb, use asyncExec() as long as you don’t depend on the result of the scheduled code, e.g. just updating widgets to report progress. If the scheduled code returns something relevant for the further control flow – e.g. prompts for an input in a blocking dialog – then I would opt for syncExec().
If, for example, a background thread wants to report progress about the work done, the simplest form might look like this:
progressBar.getDisplay().asyncExec( new Runnable() { public void run() { progressBar.setSelection( ticksWorked ); } } );
asyncExec() schedules the runnable to be executed on the UI thread ‘at the next reasonable opportunity’ (as the JavaDoc puts it).
Unfortunately, the above code will likely fail now and then with a widget disposed exception, or more precisely with an SWTException with code == SWT.ERROR_WIDGET_DISPOSED.
The reason therefore is, that the progress bar might not exist any more when it is accessed (i.e. setSelection() is called). Though we still hold a reference to the widget it isn’t of much use since the widget itself is disposed. The solution is obvious: the code must first test if the widget still exists before operating on it:
progressBar.getDisplay().asyncExec( new Runnable() { public void run() { if( !progressBar.isDisposed() ) { progressBar.setSelection( workDone ); } } } );
As obvious as it may seem, as tedious it is to implement such a check again and again. You may want to search the Eclipse bugzilla for ‘widget disposed’ to get an idea of how frequent this issue is. Therefore we extracted a helper class that encapsulates the check
new UIThreadSynchronizer().asyncExec( progressBar, new Runnable() { public void run() { progressBar.setSelection( workDone ); } } );
The UIThreadSynchronizers asyncExec() method expects a widget as its first parameter that serves as a context. The context widget is meant to be the widget that would be affected by the runnable or a suitable parent widget if more than one widget are affected. Right before the runnable is executed, the context widget is checked. If is is still alive (i.e. not disposed), the code will be executed, otherwise, the code will be silently dropped. Though the behavior to ignore code for disposed widgets may appear careless, it worked for all situations we encoutered so far.
Unit testing code that does inter-thread communication is particularly hard to test. Therefore the UIThreadSynchronizer – though it is stateless – must be instantiated to be replacable through a test double.
- The source code with corresponding tests can be found here: https://gist.github.com/rherrmann/7324823630a089217f46
While the examples use asncExec(), the UIThreadSynchronizer also supports syncExec(). And, of course, the helper class is also compatible with RAP/RWT.
If you read the source code arefully you might have noticed that there is a possible race condition. Because none of the methods of class Widget is meant to be thread-safe, the value returned by isDisposed() or getDisplay() may be stale (see line 51 and line 60). This is deliberately ignored at that point in time – read: I haven’t found any better solution. Though the runnable could be enqueued mistakenly, the isDisposed()-check (which is executed on the UI thread) would eventually prevent the code from being executed.
And there is another (admittedly small) chance for a threading issue left: right before (a)syncExec() is called the display is checked for disposal in order to not run into a widget disposed exception. But exactly that may happen if the display gets disposed in between the check and the invocation of (a)syncExec(). While this could be solved for asyncExec() by wrapping the call into a try-catch block that ignores widget disposed exceptions, the same approach fails for syncExec(). The SWTExceptions thrown by the runnable cannot be distinguished from those thrown by syncExec() with reasonable effort.
Reference: | How to Safely Use SWT’s Display asyncExec from our JCG partner Rudiger Herrmann at the Code Affine blog. |