次のユースケースがあります。
スレッドA(EDTではない)でコードを実行しています。次に、ユーザーに質問したいのですが、Swingコード(ダイアログを開くなど)が含まれるため、EDTでこれを行う必要があります。最後に、ユーザーの回答をスレッドAに戻し、続行できるようにします。
ユーザーの回答をスレッドAに返す良い方法を見つけるのに苦労しています。これをどのように行いますか?
次のユースケースがあります。
スレッドA(EDTではない)でコードを実行しています。次に、ユーザーに質問したいのですが、Swingコード(ダイアログを開くなど)が含まれるため、EDTでこれを行う必要があります。最後に、ユーザーの回答をスレッドAに戻し、続行できるようにします。
ユーザーの回答をスレッドAに返す良い方法を見つけるのに苦労しています。これをどのように行いますか?
FutureTask<Integer> dialogTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override public Integer call() {
return JOptionPane.showConfirmDialog(...);
}
});
SwingUtilities.invokeLater(dialogTask);
int result = dialogTask.get();
スレッドA内から、SwingUtilities.invokeAndWait(Runnable)を使用して、EDTでユーザープロンプトを実行できます。これにより、ランナブルが完了するまで(つまり、ユーザーが結果を送信してどこかに保存するまで)スレッドAがブロックされます。ランナブルは、スレッドAが制御を取り戻すと、スレッドAがアクセスできる場所に結果を格納するように作成できます。
EventQueue#invokeAndWait
基本的には(AKA )を使用する必要がありますSwingUtilities#invokeAndWait
。これにより、runメソッドが戻るまで現在のスレッドがブロックされます。
本当のトリックは、戻り値を取得できるようにセットアップしようとすることです;)
public class TestOptionPane03 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
String choice = ask("Chocolate", "Strewberry", "Vanilla");
System.out.println("You choose " + choice);
}
}).start();
}
public static String ask(final String... values) {
String result = null;
if (EventQueue.isDispatchThread()) {
JPanel panel = new JPanel();
panel.add(new JLabel("Please make a selection:"));
DefaultComboBoxModel model = new DefaultComboBoxModel();
for (String value : values) {
model.addElement(value);
}
JComboBox comboBox = new JComboBox(model);
panel.add(comboBox);
int iResult = JOptionPane.showConfirmDialog(null, panel, "Flavor", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
switch (iResult) {
case JOptionPane.OK_OPTION:
result = (String) comboBox.getSelectedItem();
break;
}
} else {
Response response = new Response(values);
try {
SwingUtilities.invokeAndWait(response);
result = response.getResponse();
} catch (InterruptedException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
return result;
}
public static class Response implements Runnable {
private String[] values;
private String response;
public Response(String... values) {
this.values = values;
}
@Override
public void run() {
response = ask(values);
}
public String getResponse() {
return response;
}
}
}
この例では、基本的に、実装する独自のクエリオブジェクトを作成Runnable
し、ユーザーからの応答を保存できます。
jtahlbornの答えに追加するために、次の便利なメソッドを作成しました。EDTのブロックを回避するためのチェックを追加し、合理化された優れた例外処理を提供します。
/**
* executes the given callable on the EDT, blocking and returning the result of the callable.call() method.
*
* If call() throws an exception, it is rethrown on the the current thread if the exception is either a RuntimeException, or the
* class that is assignable to exceptionClass. Otherwise, it is wrapped in a RuntimeException and thrown on the current thread.
*
* @param exceptionClass The class of any exception that may be thrown by the callable.call() method, which will now be thrown
* directly by this method (ie, not wrapped in an ExecutionException)
*/
public static <T, E extends Exception> T invokeAndWaitAndReturn(Callable<T> callable, Class<E> exceptionClass)
throws InterruptedException, E {
if (SwingUtilities.isEventDispatchThread()) {
try {
return callable.call();
}
catch (Exception e) {
throw throwException(exceptionClass, e);
}
}
else {
FutureTask<T> task = new FutureTask<T>(callable);
SwingUtilities.invokeLater(task);
try {
return task.get();
}
catch (ExecutionException ee) {
throw throwException(exceptionClass, ee.getCause());
}
}
}
@SuppressWarnings("unchecked")
private static <E extends Exception> E throwException(Class<E> exceptionClass, Throwable t) {
if (exceptionClass.isAssignableFrom(t.getClass())) {
return (E) t;
}
else if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
else {
throw new RuntimeException(t);
}
}
あなたはそれをこのように呼びます、そしてあなたが現在EDTで実行しているかどうかを心配する必要はありません:
try {
Integer result = invokeAndWaitAndReturn(new Callable<Integer>() {
public Integer call() throws MyException {
// do EDT stuff here to produce the result
}
}, MyException.class);
} catch(InterruptedException ie) {
Thread.currentThread().interrupt();
} catch(MyException me) {
// handle the "expected" Exception here
}
このutilメソッドは、サプライヤにあることを別のスイングスレッドで実行し、応答するまで待機します。次の場合にも例外がスローされます。
public class InvokeAndGet {
public static <T> T execute(Supplier<T> supplier, long timeout) throws InterruptedException, SyncException {
AtomicReference<T> atomicRef = new AtomicReference<>();
AtomicReference<Exception> atomicException = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
SwingUtilities.invokeLater(() -> {
try {
atomicRef.set(supplier.get());
}catch(Exception e){
atomicException.set(e);
}finally {
latch.countDown();
}
});
latch.await(timeout, TimeUnit.MILLISECONDS);
if(atomicException.get() != null) {
throw new SyncException(atomicException.get());
}else {
return atomicRef.get();
}
}
@SuppressWarnings("serial")
public static class SyncException extends Exception {
public SyncException(Throwable arg0) {
super(arg0);
}
}
}
そしてここに2つのテストがあるので、それを使用する方法を見ることができます:
@Test
public void execute() throws InterruptedException, SyncException {
Integer result = InvokeAndGet.execute(() -> 1+1, 5000);
assertEquals(2, result.intValue());
}
@Test(expected = SyncException.class)
public void executeException() throws InterruptedException, SyncException {
InvokeAndGet.execute(() -> 1/0, 5000);
}
この実装はSwingUtilitiesで中継され、場合によってはThreadExecutorを使用する必要があるため、より一般的にするために改善する余地がまだあります。