How to Safely Use SWT’s Display asyncExec
Most user interface (UI) toolkits are single-threaded and SWT is no exception. This restriction 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 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 a given Runnable 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() blocks the calling thread until the code has been executed on the UI thread.
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 anymore 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 of. 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 is affected. Right before the runnable is executed, the context widget is checked and if it is still alive (i.e. not disposed of), the code will be executed. Otherwise, the code will be silently dropped. Though the behavior to ignore code for disposed of widgets may appear careless, it worked for all situations we encountered 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 replaceable through a test double.
The source code with corresponding tests can be found here:
https://gist.github.com/rherrmann/7324823630a089217f46
Since 2015-01-10, the code is also part of the Xiliary project and can be used after declaring a dependency on the com.codeaffine.eclipse.swt bundle/library.
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 carefully, 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 not to run into a widget disposed exception. But exactly that may happen if the display gets disposed of in between the check and the invocation of (a)syncExec(). While this could be solved for asyncExec() by wrapping the call in 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.
Give it a try and let me know what you think. If you have comments, please leave a message here. Do not comment on the gist if you expect a reply in a timely manner. Unfortunately, comments on gists do not trigger notifications.
- Extras for Eclipse: Neon Update - 6. July 2016
- What’s the Difference? Creating Diffs with JGit - 16. June 2016
- Terminate and Relaunch in Eclipse - 19. April 2016