如何将结果从EDT传递回另一个线程?

我有以下用例:

我在线程A(不是EDT)中执行代码。 然后我想问用户一个问题,但这必须在EDT上完成,因为它涉及Swing代码(打开一个对话框等)。 最后,我想将用户的答案传递给线程A,因此它可以继续。

我很难找到一个好方法将用户的答案传递给线程A.你怎么做到这一点?

FutureTask dialogTask = new FutureTask(new Callable() { @Override public Integer call() { return JOptionPane.showConfirmDialog(...); } }); SwingUtilities.invokeLater(dialogTask); int result = dialogTask.get(); 

在线程A中,您可以使用SwingUtilities.invokeAndWait(Runnable)在EDT上执行用户提示。 这将阻止线程A直到您的runnable完成(即,直到用户提交了结果并且您已将其存储在某处)。 一旦线程A重新获得控制权,就可以编写runnable以将结果存储在线程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,并提供了一个很好的流式内核exception处理:

 /** * 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 invokeAndWaitAndReturn(Callable callable, Class exceptionClass) throws InterruptedException, E { if (SwingUtilities.isEventDispatchThread()) { try { return callable.call(); } catch (Exception e) { throw throwException(exceptionClass, e); } } else { FutureTask task = new FutureTask(callable); SwingUtilities.invokeLater(task); try { return task.get(); } catch (ExecutionException ee) { throw throwException(exceptionClass, ee.getCause()); } } } @SuppressWarnings("unchecked") private static  E throwException(Class 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() { 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方法在一个单独的swing线程中执行供应商中的操作,并等到响应。 如果存在以下情况,它也会抛出exception:

 public class InvokeAndGet { public static  T execute(Supplier supplier, long timeout) throws InterruptedException, SyncException { AtomicReference atomicRef = new AtomicReference<>(); AtomicReference 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); } } } 

这里有两个测试,所以你可以看到如何使用它:

 @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。