I would recommend taking a look at using FutureCallbacks which are part of the Guava library.
What this will allow you to do is create a ListenableFuture
for each task that you fire off. In your case, it sounds like this would be represented by a Runnable
, but you can just as easily use a Callable
. When these tasks are all fired off, you will end up with a list of these ListenableFuture
objects, which can be "flattened" into a single ListenableFuture
which represents the completion of ALL of the tasks. This is accomplished with the method Futures.allAsList(...)
:
final List<ListenableFuture<T>> futures = ...
final ListenableFuture<List<T>> combinedFuture = Future.allAsList(futures);
Now that you have a single ListenableFuture
which represents the completion of all of your tasks, you can easily listen for its completion by adding a FutureCallback
to be invoked upon completion:
Futures.addCallback(future, new FutureCallback<List<String>>() {
@Override
public void onFailure(final Throwable arg0) {
// ...
}
@Override
public void onSuccess(final List<String> arg0) {
// ...
}
}
Now, once these tasks are completed, we need to update the UI to notify users. To do so, we must be sure that the UI updates happen back on the SWT UI thread:
Display.getCurrent().asyncExec(new Runnable() {
@Override
public void run() {
// Update the UI
}
});
Note that this can easily be done within the onSuccess() method above so that the result of the tasks can be used.
Putting it all together, we can easily loop through a handful of ListeningExecutorService
.submit(...) calls for background execution (so as not to block the UI thread - In my example below, you can freely type in the text box while the tasks are running in the background), grab all the ListenableFutures
, and add a callback to be invoked upon completion, which will hop back to the UI thread to make the UI updates.
Full example:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
public class CallbackExample {
private final Display display;
private final Shell shell;
private final Text output;
private final ListeningExecutorService executor;
public CallbackExample() {
display = new Display();
shell = new Shell(display);
shell.setLayout(new FillLayout());
executor = MoreExecutors.listeningDecorator(Executors
.newFixedThreadPool(20));
final Composite baseComposite = new Composite(shell, SWT.NONE);
baseComposite
.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
baseComposite.setLayout(new GridLayout());
output = new Text(baseComposite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
output.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Button button = new Button(baseComposite, SWT.PUSH);
button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
button.setText("Start tasks");
button.addSelectionListener(new SelectionAdapter() {
@SuppressWarnings("synthetic-access")
@Override
public void widgetSelected(final SelectionEvent e) {
// Start tasks when the button is clicked
startTasks();
}
});
}
private void startTasks() {
// Create a List to hold the ListenableFutures for the tasks
final List<ListenableFuture<String>> futures = new ArrayList<ListenableFuture<String>>();
// Submit all the tasks for execution (in this case 100)
for (int i = 0; i < 100; i++) {
final ListenableFuture<String> future = executor
.submit(new Callable<String>() {
@Override
public String call() throws Exception {
// Do the work! Here we sleep to simulate a long task
Thread.sleep(2000);
final long currentMillis = System
.currentTimeMillis();
System.out.println("Task complete at "
+ currentMillis);
return "Task complete at " + currentMillis;
}
});
// Add the future for this task to the list
futures.add(future);
}
// Combine all of the futures into a single one that we can wait on
final ListenableFuture<List<String>> future = Futures
.allAsList(futures);
// Add the callback for execution upon completion of ALL tasks
Futures.addCallback(future, new FutureCallback<List<String>>() {
@Override
public void onFailure(final Throwable arg0) {
System.out.println("> FAILURE");
}
@SuppressWarnings("synthetic-access")
@Override
public void onSuccess(final List<String> arg0) {
System.out.println("> SUCCESS");
// Update the UI on the SWT UI thread
display.asyncExec(new Runnable() {
@Override
public void run() {
final StringBuilder sb = new StringBuilder();
for (final String s : arg0) {
sb.append(s + "\n");
}
final String resultString = sb.toString();
output.setText(resultString);
}
});
}
});
}
public void run() {
shell.setSize(200, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
executor.shutdownNow();
display.dispose();
}
public static void main(final String... args) {
new CallbackExample().run();
}
}