【问题标题】:How to pass results from EDT back to a different thread?如何将 EDT 的结果传递回不同的线程?
【发布时间】:2012-11-20 03:38:16
【问题描述】:

我有以下用例:

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

我正在努力寻找一种将用户的答案传回线程 A 的好方法。您是如何做到的?

【问题讨论】:

    标签: java swing event-dispatch-thread


    【解决方案1】:
    FutureTask<Integer> dialogTask = new FutureTask<Integer>(new Callable<Integer>() {
      @Override public Integer call() {
        return JOptionPane.showConfirmDialog(...);
      }
    });
    SwingUtilities.invokeLater(dialogTask);
    int result = dialogTask.get();
    

    【讨论】:

    • 这很有趣...我想知道这将如何处理 call() 方法中抛出的异常?我希望在原始(不是 EDT)线程上抛出异常(如果有的话)......
    • @EricLindauer - 所有这些都完全按照您的预期处理。异常将在包装在 ExecutionException 中的原始线程中引发(有关更多详细信息,请参阅Future.get())。
    • 注意:当您调用此代码时,您必须确保您不在 EDT 上,因为 get() 永远不会返回,并且 EDT 永远不会被释放以执行 FutureTask...
    【解决方案2】:

    在线程 A 中,您可以使用 SwingUtilities.invokeAndWait(Runnable) 在 EDT 上执行您的用户提示。这将阻塞线程 A,直到您的可运行文件完成(即,直到用户提交结果并且您将其存储在某处)。一旦线程 A 重新获得控制权,您的 runnable 可以被写入以将结果存储在线程 A 可以访问的位置。

    【讨论】:

    • 对...所以“将结果存储在某个地方”部分确实是我要问的...有没有一种优雅的方法来完成这个?
    • FutureTask / SwingUtilities.invokeLater() 和 task.get() 似乎可以很好地完成目标,无需编写任何额外的代码来存储/检索线程之间的结果。
    【解决方案3】:

    基本上,您需要使用EventQueue#invokeAndWait(又名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,并且可以存储来自用户的响应

    【讨论】:

    • 好的,这基本上就是我想出的。我创建了一个通用的 Response ,其 getter 和 setter 与您的想法非常相似。我还必须为其添加异常以将错误传递回线程 A...这似乎很笨拙,但也许这是唯一的方法...感谢您的宝贵时间!
    • 这也是我最好的尝试,但是使用接受的答案中显示的 FutureTask 会好得多。不管怎么说,多谢拉! :)
    【解决方案4】:

    我编写了以下便捷方法来添加到 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
    }
    

    【讨论】:

    • 当然,编写不带 Exception 参数或 2 或 3 的“invokeAndWait”版本很容易!
    • “无异常”的便利方法是: public static T invokeAndWaitAndReturn(Callable callable) throws InterruptedException { return invokeAndWaitAndReturn(callable, RuntimeException.class); }
    【解决方案5】:

    这个 util 方法在一个单独的 swing 线程中执行供应商中的操作,并等待响应。如果存在以下情况,它也会抛出异常:

    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);
        }
    }
    
    }
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-22
      • 1970-01-01
      相关资源
      最近更新 更多