站内搜索: 请输入搜索关键词
当前页面: 图书首页 > SWT: The Standard Widget Toolkit

5.7 Multithreaded Programming - SWT: The Standard Widget Toolkit

Previous Section  < Day Day Up >  Next Section

5.7 Multithreaded Programming

In SWT, by definition, the thread that creates the display is called the user interface thread. This thread is responsible for reading and dispatching events from the operating system event queue and invoking listeners in response. You can find out which thread is the user interface thread for a particular display by calling the getThread() method on the display:

getThread() Returns the thread that is running the event loop on that display.

You can test whether your code is running in the user interface as follows.






if (display.getThread() == Thread.currentThread()) {

    // current thread is the user interface thread

}


Listener code is always executed in the user interface thread. This makes SWT applications generally quite responsive, behaving like most other native applications on the desktop. However, any long operation, when executed directly by a listener, will prevent the user interface thread from reading and dispatching other events. This gives the program the appearance of being hung while the operation is running.

If a listener has a large amount of work to perform, instead of doing the work in the user interface thread, it must fork a separate thread so that the user interface thread can continue dispatching events. If this other thread tries to execute code that accesses a widget, for example, to change the string in a label, there is a concurrency issue. Most operating systems do not support having multiple threads concurrently accessing the state of widgets. Typically, attempting to do this will cause crashes, hangs, or simply unpredictable behavior. Some kind of synchronization is necessary.

5.7.1 Apartment Threading

SWT implements a single-threaded user interface model that is typically called apartment threading. In this model, only the user interface thread can invoke user interface operations. This rule is strictly enforced. If you try to access an SWT object from outside the user interface thread, you will get an SWTException("Invalid thread access").

Different operating systems have diverse rules governing threads, widgets, and synchronization. Some use an apartment-threaded model such as SWT. Others allow any thread to invoke user interface operations but allow only one thread at a time in the window system library, controlling access through a global lock. This type of multithreaded user interface model is typically called free threading. In order to be simple, efficient, and (most important) portable, SWT is apartment-threaded. This is the only model that can be implemented on all platforms.

To allow background threads to perform operations on objects belonging to the user interface thread, the Display methods asyncExec() and syncExec() are used.

asyncExec(Runnable runnable) Causes the argument to be run by the user interface thread of the display.

syncExec(Runnable runnable) In addition to causing the argument to be run by the user interface thread of the display, this method causes the current thread (if it is different than the user interface thread of the display) to wait for the runnable to finish.

Along with wake(), the asyncExec() and syncExec() methods are among a handful of methods in the widgets package that can be called from any thread.

The syncExec() or asyncExec() Methods Do Not Create Threads

Beginning SWT developers sometimes get confused by the fact that syncExec() and asyncExec() take a runnable as an argument. They expect these methods to create a new thread or run in some other thread instead of executing the runnable in the user interface thread. This is not the case. If you perform a long operation inside a syncExec() or asyncExec(), you will temporarily hang the user interface in the same manner as invoking the long operation from a listener.


Both asyncExec() and syncExec() cause their arguments to be run by the user interface thread at the next "reasonable" opportunity. This occurs during readAndDispatch(), when no operating system events are available to be processed. At this point, if there are runnables pending, they are removed from the queue and sent the run() message. When run() returns, readAndDispatch() returns true, ensuring that it will be called again by the event loop to handle either any new events that are available or any other runnables that are pending.

Note that this process favors operating system events over code that is executed via asyncExec() and syncExec(). Because of this, the same caveat applies as was noted in the section Flushing the Event Queue: An arbitrary number of operating system events may be processed before your runnable is evaluated, so the state of the user interface may be different than you expect.

Using syncExec() to Run Code in the User Interface Thread

As noted above, when syncExec() is called from a background thread, the runnable is executed in the user interface thread, and the background thread waits for the result. Thus, runnables executed by syncExec() most closely match the equivalent "direct call" to the widget and should be used when you would like simply to call a widget method but cannot because of the apartment-threading model.

Here is a variation of the code from the section Sleeping and Waking that uses syncExec() to show progress from the background thread. In this case, the shell title is updated as each unit of work is completed, rather than simply writing to the console.






public static void main(String[] args) {

    final Display display = new Display();

    final Shell shell = new Shell(display);

    shell.setSize(500, 64);

    shell.open();

    final boolean[] done = new boolean[1];

    new Thread() {

        public void run() {

            for (int i = 0; i < 10; i++) {

                try {

                    Thread.sleep(500);

                } catch (Throwable th) {

                }

                display.syncExec(new Runnable() {

                    public void run() {

                        if (shell.isDisposed()) return;

                        shell.setText(shell.getText()+".");

                    }

                });

            }

            done[0] = true;

            // wake the user interface thread from sleep

            display.wake();

        }

    }

    .start();

    shell.setText("Running ");

    while (!done[0]) {

        if (!display.readAndDispatch()) display.sleep();

    }

    if (!shell.isDisposed()) {

        shell.setText(shell.getText() + " done.");

        try {

            Thread.sleep(500);

        } catch (Throwable th) {

        }

    }

    display.dispose();

}


Note that using syncExec() ensures that the user interface thread and the background thread are synchronized at the time that the runnable is executed. In the example code, one period is used to represent 10% of work. For example, when the background thread has completed 70% of the work, it sets the title of the shell to seven periods, waiting for the user interface thread to update before proceeding with the other 30% of the work.

Using asyncExec() to Queue Code for the User Interface Thread

In the same manner as syncExec(), asyncExec() executes a runnable in the user interface thread. Instead of waiting for the result, asyncExec() queues the runnable and returns right away, allowing the background thread to keep running. Because asyncExec() does not wait, the user interface thread and the background thread execute independently.

The example in the section Using syncExec() to Run Code in the User Interface Thread can be recoded to use asyncExec() by simply changing the line






display.syncExec(new Runnable() {


to






display.asyncExec(new Runnable() {


Running this new version seems to give the same result as the original one, but there is a subtle difference.

When syncExec() is used, the user interface always shows the amount of work that the background thread has completed at the time the work is completed. This seems to be exactly what you want until you realize that the cost of this is that no new work will be started until the user interface thread can successfully indicate the completion of the current work. If the user interface thread is processing some long-running operation at the time syncExec() is called or if many operating system events are waiting to be dispatched, the background thread processing will be delayed.

Using asyncExec(), the background thread tells the user interface thread to indicate the progress but then continues immediately, executing the next element of work. This ensures that the background thread is not delayed by the user interface thread but leads to a new problem: If the background thread continues to finish work faster than the user interface thread can display the progress, more and more runnables will be queued. This situation is called flooding the user interface, which has several effects.

The user interface does not accurately display the amount of progress that has been made. The problem here is that the background thread may have completely finished its work before the user interface thread has reported the first amount of progress. What is worse, even though the task is complete by then, the user interface continues to display every interim progress state until all runnables have been processed.

The overall performance of the user interface is impacted. Time is wasted processing each of the runnables, even though they are no longer relevant. The importance of this should not be underestimated. Running code in the user interface thread can make the user interface thread slow to process operating system events, causing the user interface to become sluggish.

Consistency problems become more likely. Because runnables passed to asyncExec() (and syncExec()) are executed only when no operating system events are available, if there are many runnables to be processed, it becomes increasingly likely that something will have changed by the time the runnable is evaluated.

When to Use syncExec() and asyncExec()

Because it appears that both syncExec() and asyncExec() can potentially cause problems, which one should you use?

For most simple situations, use syncExec(). As long as the rest of your user interface is responsive, the cost of waiting for the runnable to be processed will be low enough that it will not impact the performance of your background thread significantly, particularly if you report progress (or whatever user interface painting you are doing) only after a certain minimum length of time has passed. For example, if you have reported progress in the last 0.01 of a second, you can probably avoid reporting it again now (unless intervals that short really are of interest in your particular application).

Unfortunately, there is another problem with syncExec() that can make it unusable in some situations: It can cause deadlock. Consider a multi-threaded application where access to some of the application resources is being managed by synchronization locks. In this situation, the following scenario could occur.

The background thread acquires a lock. Meanwhile, the user pushes a button that causes the user interface thread to try to acquire the same lock. Next, the background thread does a syncExec() to execute code in the user interface. The background thread is left waiting for the user interface thread to process the syncExec() while the user interface is waiting for the background thread to release the lock. Neither thread will ever proceed, causing the application to be deadlocked.


If, however, the background thread had used asyncExec(), it would have continued running, eventually releasing the lock it held so that the user interface thread could acquire the lock, finish processing the button press event, and finally handle the runnable passed to it by the asyncExec() call.

It should be noted that deadlock problems are possible in any program that has multiple threads and more than one lock. As long as calls to syncExec() are treated as locks on the user interface thread, it is possible to code your application carefully so that it will not deadlock. This can be difficult for large systems though, particularly ones where the code is provided by many different developers and there is a large number of threads.[6]

[6] Of course, Eclipse is one such application. The prevailing wisdom in the Eclipse community is to use asyncExec() whenever possible for this very reason.

It is relatively easy to prevent flooding by using variations of the pattern "Detect whether the last asyncExec has completed before scheduling another." Rather than providing (yet another) complete version of the code from the Sleeping and Waking section, here is just the relevant part, showing one way to implement the pattern.






...

final boolean[] reporting=new boolean[1];

final StringBuffer title=new StringBuffer("Running ");

new Thread() {

    public void run() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep(500);

            } catch (Throwable th) {

            }

            title.append(".");

            if (reporting[0]) continue;

            reporting[0] = true;

            display.asyncExec(new Runnable() {

                public void run() {

                    if (shell.isDisposed()) return;

                    shell.setText(title.toString());

                    reporting[0] = false;

                }

            });

...


Notice that this code begins by building a single boolean state variable that will be true when the user interface is reporting progress and false otherwise. It then does its work as before (simulated by the Thread.sleep(500) call). After the work is completed, it checks whether there is already pending progress to be reported. If so, it simply continues doing its work. However, if progress is not currently being reported, it indicates that progress reporting has started (reporting[0]=true), then uses asyncExec, rather than syncExec, to queue the progress indication. When the runnable is eventually evaluated, it updates the shell title, then finally indicates that progress reporting has finished. In this way, there will never be more than one progress-reporting runnable pending at any given time.

One subtle point to notice about this version of the code is that it must keep track of the amount of progress that has been completed梚n this case, the number of dots to display in the shell title梚n a separate StringBuffer, rather than querying the old value from the shell, and adding a dot to it. This is required because there is no longer a one-to-one mapping between the number of times work is done, and progress is reported.

Normally, both syncExec() and asyncExec() are called from background threads. When syncExec() is called from the user interface thread, the runnable is executed immediately. There is no need to queue the runnable or lock anything because the current thread is the user interface thread. Calling syncExec() from the user interface thread is not very useful or harmful but is supported for completeness. It is interesting to note that asyncExec(), when called from the user interface thread, can be useful because it queues the work, causing it to happen at a later time.

Using asyncExec() from the User Interface Thread

Some operating systems have the concept of idle handlers, which allow you to execute code when the user interface becomes idle. As we noted in the Apartment Threading section, to make the user interface as responsive as possible, runnables queued by asyncExec() are evaluated only when there are no pending operating system events to be processed. This means that asyncExec(), when called from the user interface thread, will cause the runnable to be executed after readAndDispatch() has dispatched all outstanding events but before the event loop calls sleep(), making them equivalent to idle handlers.

The following code fragment calls asyncExec() from the user interface thread every time a key is pressed in a text control. To ensure that the text control has processed the key, before querying the contents of the control and setting it into a label, the code uses asyncExec().






public static void main(String[] args) {

    final Display display = new Display();

    Shell shell = new Shell(display);

    shell.setLayout(new FillLayout(SWT.VERTICAL));

    final Text text =

        new Text(shell, SWT.BORDER | SWT.SINGLE);

    final Label label = new Label(shell, SWT.BORDER);

    text.addListener(SWT.KeyDown, new Listener() {

        public void handleEvent(Event event) {

            display.asyncExec(new Runnable() {

                public void run() {

                    label.setText(

                        "\"" + text.getText() + "\"");

                }

            });

        }

    });

    shell.setSize(shell.computeSize(400, SWT.DEFAULT));

    shell.open();

    while (!shell.isDisposed()) {

        if (!display.readAndDispatch()) display.sleep();

    }

    display.dispose();

}


The call to asyncExec() in this example is critical, because the native text widget processes the keystroke only after all KeyDown listeners have been invoked. Thus, without the asyncExec() call, the key would not yet have been added to the text widget contents when the text.getText() call in the listener was made. This causes the contents of the label to be always one or more characters behind what the text control was showing. To see this bug, try changing the asyncExec() to a syncExec().

Of course, a runnable that calls asyncExec() on itself will run repeatedly, stopping the event loop from sleeping in the operating system.[7] Essentially, the program will never become idle because it is continually running an idle handler. This is equivalent in terms of CPU usage to busy waiting with readAndDispatch() (see the section Reading and Dispatching).

[7] But not hanging the user interface, because readAndDispatch() will always dispatch any available operating system events before running the runnable again.

A better way to run code repeatedly is to use a timer.

    Previous Section  < Day Day Up >  Next Section