Let me see if I understand the problem correctly. You have a FIFO queue of tasks, only the oldest of which is running. Each task needs to update the UI when it's done. But if a certain user event comes in, all tasks need to be cancelled -- that is, running tasks need to be cancelled, and not-yet-running tasks need to be removed from the queue. Is that right?
Assuming it is, I wouldn't use SwingWorker
since you only need one worker thread, not one per task. FutureTask
should be enough (assuming you override done()
to make the necessary call to SwingUtilities.invokeLater()
and do the UI update).
If you cancel a FutureTask
, then even if its run()
method gets called, it won't do anything. So you can submit FutureTask
s safely to a ExecutorService
knowing that cancellation will work even if the executor tries to run them.
I suspect that a good-enough solution would just be to keep a list of all FutureTasks
that might need to be cancelled, and cancel them all when the user event comes in. The ExecutorService
will still try to run them but it'll basically be a no-op. You need to make sure completed tasks are removed from the list, and you need to make sure the list is updated and used in a thread-safe way (probably from the same thread that puts tasks on the ExecutorService
), but that shouldn't be too hard.
I bashed the code below out in just an hour and I wouldn't bet on it being correct, but you get the idea. :)
/** Untested code! Use at own risk. */
public class SwingTaskExecutor {
// ////////////////////////////////////////////////////////////
// Fields
private final ExecutorService execSvc = Executors.newFixedThreadPool(1);
private final Lock listLock = new ReentrantLock();
private final List<ManagedSwingTask<?>> activeTasks =
new ArrayList<ManagedSwingTask<?>>();
// ////////////////////////////////////////////////////////////
// Public methods
public <T> Future<T> submit(SwingTask<T> task) {
ManagedSwingTask<T> managedTask = new ManagedSwingTask<T>(task);
addToActiveTasks(managedTask);
execSvc.submit(managedTask);
return managedTask;
}
public void cancelAllTasks() {
listLock.lock();
try {
for (ManagedSwingTask<?> t: activeTasks) {
t.cancel(true);
}
activeTasks.clear();
} finally {
listLock.unlock();
}
}
// ////////////////////////////////////////////////////////////
// Private methods
private <T> void addToActiveTasks(ManagedSwingTask<T> managedTask) {
listLock.lock();
try {
activeTasks.add(managedTask);
} finally {
listLock.unlock();
}
}
// ////////////////////////////////////////////////////////////
// Helper classes
private class ManagedSwingTask<T> extends FutureTask<T> {
private final SwingTask<T> task;
ManagedSwingTask(SwingTask<T> task) {
super(task);
this.task = task;
}
@Override
public void cancel(boolean mayInterruptIfRunning) {
try {
task.cancel();
} finally {
super.cancel(mayInterruptIfRunning);
}
}
@Override
protected void done() {
removeFromActiveTasks();
updateUIIfDone();
}
private void removeFromActiveTasks() {
listLock.lock();
try {
activeTasks.remove(this);
} finally {
listLock.unlock();
}
}
private void updateUIIfDone() {
if (isDone()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
task.updateUI();
}
});
}
}
}
public static interface SwingTask<T> extends Callable<T> {
/** Called from the EDT if task completes successfully */
void updateUI();
/** Hook in case there's task-specific cancellation to be done*/
void cancel();
}
}
Something like that, anyway.
If you want to be doubly sure, you could then shut down and replace the ExecutorService
, but that's probably not necessary.