I am trying to implement a SwingWorker
class within my application. Is there a way to set a length of time that after which, the SwingWorker \"times out\"? I
The short answer is "it's hard", depending on what your requirements are. I highly recommend reading Java Concurrency In Practice.
The basic thing you could do would be to (a) make sure your SwingWorker's Runnable is interrupt-friendly, and (b) set a Timer (or use the blocking get() call Brian mentioned) to cancel your Future.
The inner class MySwingWorker may well do what you need:
package com.misc;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class FutureStuffGUI extends JFrame {
/**
* Provides a variant of SwingWorker which operates with a timeout.
*
* @param <T>
*/
private static abstract class MySwingWorker<T> {
private T result;
private Exception raised;
/**
* Constructor.
*
* @param timeout
* @param timeUnit
*/
public MySwingWorker(final long timeout, final TimeUnit timeUnit) {
result = null;
raised = null;
System.out.println(Thread.currentThread().getName() + " starting");
final FutureTask<T> future = new FutureTask<T>(new Callable<T>() {
public T call() throws Exception {
System.out.println(Thread.currentThread().getName() + " running");
T result = doInBackground();
return result;
}
});
System.out.println(Thread.currentThread().getName() + " future: " + future);
final Thread runner = new Thread(null, future, "FutureThread");
Thread watcher = new Thread(null, new Runnable() {
@Override
public void run() {
runner.start();
try {
result = future.get(timeout, timeUnit);
} catch (Exception ex) {
raised = ex;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
assert SwingUtilities.isEventDispatchThread();
done();
}
});
}
}, "WatcherThread");
watcher.start();
}
/**
* Implement this method as the long-running background task.
*
* @return
* @throws Exception
*/
abstract protected T doInBackground() throws Exception;
/**
* This method is invoked from the UI Event Dispatch Thread on completion or timeout.
*/
abstract protected void done();
/**
* This method should be invoked by the implementation of done() to retrieve
* the result.
*
* @return
* @throws Exception
*/
protected T get() throws Exception {
assert SwingUtilities.isEventDispatchThread();
if (raised != null) {
throw raised;
} else {
return result;
}
}
}
public FutureStuffGUI() {
super("Hello");
init_components();
}
private void init_components() {
JPanel panel = new JPanel();
JButton button = new JButton("Press");
panel.add(button);
add(panel);
pack();
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new MySwingWorker<String>(5, TimeUnit.SECONDS) {
@Override
protected String doInBackground() throws InterruptedException {
assert !SwingUtilities.isEventDispatchThread();
System.out.println(Thread.currentThread().getName() + " doInBackground");
// if (true) { throw new RuntimeException("Blow up"); }
Thread.sleep(6 * 1000);
return "Hello world!";
}
@Override
protected void done() {
assert SwingUtilities.isEventDispatchThread();
String result;
try {
result = get();
System.out.println(Thread.currentThread().getName() + " done; result: " + result);
} catch (Exception ex) {
System.out.println(Thread.currentThread().getName() + " done; errored:");
ex.printStackTrace();
}
}
};
};
});
}
public static void main(String[] args) {
FutureStuffGUI ui = new FutureStuffGUI();
ui.setVisible(true);
}
}
Any particular reason for not using the SwingWorker.get(long, java.util.concurrent.TimeUnit) ? It will throw TimeoutException that you can easily handle in your application.
Why not embed your task within a Runnable, drop it into a new single-threaded ExecutorService and then perform a get()
on the resulting Future with an appropriate timeout. That would give you your timeout functionality, since get()
will throw an exception if the job doesn't complete in time.