8

我有以下用例:

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

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

4

5 回答 5

18
FutureTask<Integer> dialogTask = new FutureTask<Integer>(new Callable<Integer>() {
  @Override public Integer call() {
    return JOptionPane.showConfirmDialog(...);
  }
});
SwingUtilities.invokeLater(dialogTask);
int result = dialogTask.get();
于 2012-11-20T03:49:03.997 回答
2

我编写了以下便捷方法来添加到 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
}
于 2012-11-20T06:26:29.617 回答
2

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

于 2012-11-20T03:46:57.490 回答
2

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

于 2012-11-20T03:47:46.223 回答
1

这个 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。

于 2018-01-28T10:01:50.520 回答