Background Tasks

Often, an application will need to perform a long-running task (such as loading a large file or executing a computationally-intensive operation). Executing such code on the UI thread will cause the application to stop responding to repaints while the operation is running, giving the appearance that it is "hung".

Pivot includes the org.apache.pivot.util.concurrent.Task class to help resolve this problem. A Task represents an instance of an operation that may be performed by a background thread. It is an abstract class, whose execute() method is overridden by an application to perform the actual task, optionally returning a value when it is complete.

Though callers can invoke the execute() method directly, this would have the same effect as simply executing the code inline: the UI will appear to hang until execute() returns. However, Task provides an additional version of the execute() method that takes an instance of org.apache.pivot.util.concurrent.TaskListener. When this version of the method is invoked, the Task class creates (or obtains) an instance of a background thread and calls the abstract version of the method on this thread instead of the UI thread. When the abstract execute() returns, the task notifies the caller by invoking one of the methods defined by the listener interface:

public void taskExecuted(Task<V> task);
public void executeFailed(Task<V> task);

If the execute() method returns successfully, the taskExecuted() method is called, and the result of the operation can be obtained by calling the getResult() method of the task object. However, if the execute() call fails (by throwing an exception), the executeFailed() listener method is called, and the exception that was thrown can be obtained via the getFault() method of the task object.

The following example demonstrates the behavior of an application when a task is executed synchronously vs. asynchronously. It uses a simple "sleep task" to simulate an operation that runs for five seconds:

Pressing the "Execute Synchronously" button activates an activity indicator component and calls the synchronous version of the execute() method of the sleep task. Though the task runs and returns correctly, the activity indicator is never shown, because the UI thread has been unable to respond to repaints while the task was running. However, when the "Execute Asynchronously" button is pressed, the activity indicator appears correctly and runs for five seconds until the task is complete.

The BXML for this example is shown below:

            
            <backgroundtasks:BackgroundTasks title="Background Tasks" maximized="true"
                xmlns:bxml="http://pivot.apache.org/bxml"
                xmlns:backgroundtasks="org.apache.pivot.tutorials.backgroundtasks"
                xmlns="org.apache.pivot.wtk">
                <BoxPane orientation="vertical"
                    styles="{horizontalAlignment:'center', verticalAlignment:'center'}">
                    <Border styles="{padding:2}">
                        <ActivityIndicator bxml:id="activityIndicator"/>
                    </Border>

                    <PushButton bxml:id="executeSynchronousButton" buttonData="Execute Synchronously"/>
                    <PushButton bxml:id="executeAsynchronousButton" buttonData="Execute Asynchronously"/>
                </BoxPane>
            </backgroundtasks:BackgroundTasks>
            
        

The Java source for SleepTask is as follows. It simply sleeps for 5000 milliseconds and then returns a simulated result value:

            
            package org.apache.pivot.tutorials.backgroundtasks;

            import org.apache.pivot.util.concurrent.Task;
            import org.apache.pivot.util.concurrent.TaskExecutionException;

            public class SleepTask extends Task<String> {
                @Override
                public String execute() throws TaskExecutionException {
                    // Simulate a long-running activity (5s)
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException exception) {
                        throw new TaskExecutionException(exception);
                    }

                    // Return a simulated result value
                    return "Done sleeping!";
                }
            }
            
        

The Java source for the application is shown below:

            
            package org.apache.pivot.tutorials.backgroundtasks;

            import java.net.URL;

            import org.apache.pivot.beans.Bindable;
            import org.apache.pivot.collections.Map;
            import org.apache.pivot.util.Resources;
            import org.apache.pivot.util.concurrent.Task;
            import org.apache.pivot.util.concurrent.TaskExecutionException;
            import org.apache.pivot.util.concurrent.TaskListener;
            import org.apache.pivot.wtk.ActivityIndicator;
            import org.apache.pivot.wtk.Button;
            import org.apache.pivot.wtk.ButtonPressListener;
            import org.apache.pivot.wtk.PushButton;
            import org.apache.pivot.wtk.TaskAdapter;
            import org.apache.pivot.wtk.Window;

            public class BackgroundTasks extends Window implements Bindable {
                private ActivityIndicator activityIndicator = null;
                private PushButton executeSynchronousButton = null;
                private PushButton executeAsynchronousButton = null;

                @Override
                public void initialize(Map<String, Object> namespace, URL location, Resources resources) {
                    activityIndicator = (ActivityIndicator)namespace.get("activityIndicator");
                    executeSynchronousButton = (PushButton)namespace.get("executeSynchronousButton");
                    executeAsynchronousButton = (PushButton)namespace.get("executeAsynchronousButton");

                    executeSynchronousButton.getButtonPressListeners().add(new ButtonPressListener() {
                        @Override
                        public void buttonPressed(Button button) {
                            activityIndicator.setActive(true);

                            System.out.println("Starting synchronous task execution.");

                            SleepTask sleepTask = new SleepTask();

                            String result = null;
                            try {
                                result = sleepTask.execute();
                            } catch (TaskExecutionException exception) {
                                System.err.println(exception);
                            }

                            System.out.println("Synchronous task execution complete: \"" + result + "\"");

                            activityIndicator.setActive(false);
                        }
                    });

                    executeAsynchronousButton.getButtonPressListeners().add(new ButtonPressListener() {
                        @Override
                        public void buttonPressed(Button button) {
                            activityIndicator.setActive(true);
                            setEnabled(false);

                            System.out.println("Starting asynchronous task execution.");

                            SleepTask sleepTask = new SleepTask();
                            TaskListener<String> taskListener = new TaskListener<String>() {
                                @Override
                                public void taskExecuted(Task<String> task) {
                                    activityIndicator.setActive(false);
                                    setEnabled(true);

                                    System.out.println("Synchronous task execution complete: \""
                                        + task.getResult() + "\"");
                                }

                                @Override
                                public void executeFailed(Task<String> task) {
                                    activityIndicator.setActive(false);
                                    setEnabled(true);

                                    System.err.println(task.getFault());
                                }
                            };

                            sleepTask.execute(new TaskAdapter<String>(taskListener));
                        }
                    });
                }
            }
            
        

Note that the button press listener for the "Execute Asynchronously" button wraps the actual task listener in an instance of org.apache.pivot.wtk.TaskAdapter. This is because, like most UI toolkits, Pivot user interfaces are single-threaded: all UI operations must occur on the same thread (which AWT calls the "event dispatch thread"). Wrapping the task listener in a TaskAdapter ensures that the result listener will be called on the UI thread, rather than the background thread, which is what would occur if the listener was not wrapped in the adapter.

Next: Web Queries