SwingUtilites:如何从Java中的另一个线程返回值?

我正在尝试用Java创建一个应用程序。 为了使Swing正常工作,我这样做了:

public static void main(String[] array){ String outerInput; SwingUtilities.invokeLater(new Runnable(){ @Override public void run() { // I want this string input. String input = JOptionPane.showInputDialog( null,"Stop ?", JOptionPane.QUESTION_MESSAGE); }); // How can I get this input value in String outerInput? } 

我如何在我的主体中获得此输入字符串?

我如何在我的主体中获得此输入字符串?

你不会。 你的“main”调用Swing对话框然后对结果做一些事情的想法与图形用户界面的整个想法相反。

在GUI中,您可以设计程序来处理一系列用户启动的事件。 这些事件可能是完全异步的,例如典型文字处理器的击键,选择和菜单选择。 或者它们可能是脚本化的,例如“向导”的问答forms。

假设您想要执行后者之类的操作,您可以使用以下顺序实现它:

  1. 用户启动一些操作,可能选择菜单选项。 这转变为ActionListener的调用,它决定它需要来自用户的更多输入。
  2. ActionListener在事件派发线程上执行,允许对UI执行任何操作,例如显示对话框。 该对话可以是模态的或非模态的; 在一种情况下,输出可用于原始侦听器,另一种情况下,您需要编写新的侦听器以执行后续操作。
  3. 获得足够的信息后,您可以选择调用后台操作。 您通常会有一个线程池来为这些请求提供服务。 您不会尝试在“主”线程上执行请求; 事实上,对于所有意图,主线程不再运行。
  4. 当您的操作完成运行时,它将使用SwingUtilities.invokeLater()将数据推送回事件派发线程。 虽然您可以使用invokeAndWait()在后台操作过程中将结果发送到Swing,但这不是一个好主意。 相反,创建一系列操作,最好是一个易于被用户取消的操作。

在后台线程上启动操作的“标准”方法是通过SwingWorker 。 还有其他选择; 例如,您可以使用BlockingQueue将操作发送到单个长时间运行的后台线程,并使用invokeLater()返回结果。

无论如何,有一条规则你不想破坏: 永远不会在事件派发线程上执行阻塞操作 。 如果你这样做,那么你的申请就会被破坏

您可以使用AtomicReference以线程安全的方式在线程之间传递值。

正如Hemal所说,你需要在两个线程之间进行一些同步,以确保它已经被执行。 例如,您可以使用CountDownLatch或使用SwingUtilities.invokeAndWait(确保您不要从Swing线程调用它!)

更新:这是使用AtomicReference和CountDownLatch的完整示例

 public class Main { public static void main(String[] args) throws InterruptedException { final AtomicReference result = new AtomicReference(); final CountDownLatch latch = new CountDownLatch(1); SwingUtilities.invokeLater(new Runnable() { public void run() { String input = JOptionPane.showInputDialog(null, "Stop?", "Stop?", JOptionPane.QUESTION_MESSAGE); result.set(input); // Signal main thread that we're done and result is set. // Note that this doesn't block. We never call blocking methods // from Swing Thread! latch.countDown(); } }); // Here we need to wait until result is set. For demonstration purposes, // we use latch in this code. Using SwingUtilities.invokeAndWait() would // be slightly better in this case. latch.await(); System.out.println(result.get()); } } 

另请阅读有关GUI(和Swing)应用程序的一般设计的答案 。

现在你有两个线程:主线程和EDT(事件调度线程)。 我假设您知道SwingUtilities.invokeLater(runnable)正在EDT上运行任务。

要在线程之间共享数据,您只需要一些在两个线程范围内的变量。 最简单的方法是在包含main方法的类中声明volatile数据成员或AtomicReference

为了确保您在JOptionPane返回后读取值 ,您可以在这里做的最简单的事情是将invokeLater调用更改为invokeAndWait调用。 这将导致主线程停止执行,直到您放入EDT的内容完成为止。

例如:

 public class MyClass { private static volatile String mySharedData; public static void main(String[] args) throws InterruptedException { SwingUtilities.invokeAndWait(new Runnable() { public void run() { mySharedData = JOptionPane.showInputDialog(null, "Stop ?", JOptionPane.QUESTION_MESSAGE); } }); // main thread is blocked, waiting for the runnable to complete. System.out.println(mySharedData); } } 

如果你的主线程正在执行一些在选项窗格存在时不应该停止的任务,那么在主线程中你可以定期检查(即,在运行你的任务的循环的外部)是否mySharedData已设置。 如果你的任务没有循环而是做一些I / O或等待,你可以使用Thread.interrupt并在InterruptedExecption处理程序中检查mySharedData

我建议使用观察者/可观察模式,也许使用PropertyChangeListener。 然后,如果关键变量状态发生变化,您的Swing应用程序将能够通知任何和所有侦听器。

例如:

 import java.awt.*; import java.beans.*; import javax.swing.*; import javax.swing.event.*; public class ListenToSwing { public static final String STATE = "state"; private static final int STATE_MAX = 10; private static final int STATE_MIN = -10; private JPanel mainPanel = new JPanel(); private int state = 0; private JSlider slider = new JSlider(STATE_MIN, STATE_MAX, 0); public ListenToSwing() { mainPanel.add(slider); slider.setPaintLabels(true); slider.setPaintTicks(true); slider.setMajorTickSpacing(5); slider.setMinorTickSpacing(1); slider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { setState(slider.getValue()); } }); } public void addPropertyChangeListener(PropertyChangeListener listener) { mainPanel.addPropertyChangeListener(listener); } public Component getMainPanel() { return mainPanel; } public void setState(int state) { if (state > STATE_MAX || state < STATE_MIN) { throw new IllegalArgumentException("state: " + state); } int oldState = this.state; this.state = state; mainPanel.firePropertyChange(STATE, oldState, this.state); } public int getState() { return state; } public static void main(String[] args) { final ListenToSwing listenToSwing = new ListenToSwing(); SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame("ListenToSwing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(listenToSwing.getMainPanel()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); listenToSwing.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ListenToSwing.STATE)) { System.out.println("New state: " + listenToSwing.getState()); } } }); } } 

您可以使用AtomicReference和invokeAndWait。

 public static void main(String[] array){ AtomicReference outerInput = new AtomicReference(); SwingUtilities.invokeAndWait(new Runnable(){ @Override public void run() { String input = JOptionPane.showInputDialog( null,"Stop ?", JOptionPane.QUESTION_MESSAGE); outerInput.set(input); }); outerInput.get(); //Here input is returned. } 

通过声明runnable设置值的String[] ,可以将它暴露给外部类。 但请注意,您需要一些同步机制来了解它是否已由Runnable分配。

以下代码将执行您想要的操作。 我做了类似的事情,除了我正在启动JFileChooser而不是输入对话框。 我发现它比将一堆路径硬编码到我的应用程序或接受命令行参数更方便,至少为了测试目的。 我想补充一点,可以修改prompt()方法以返回FutureTask实例以增加灵活性。

 public class Question { public static void main(String[] args) { Question question = new Question(); String message = "Stop?"; System.out.println(message); // blocks until input dialog returns String answer = question.ask(message); System.out.println(answer); } public Question() { } public String ask(String message) { try { return new Prompt(message).prompt(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } return null; } private class Prompt implements Callable { private final String message; public Prompt(String message) { this.message = message; } /** * This will be called from the Event Dispatch Thread aka the Swing * Thread. */ @Override public String call() throws Exception { return JOptionPane.showInputDialog(message); } public String prompt() throws InterruptedException, ExecutionException { FutureTask task = new FutureTask<>(this); SwingUtilities.invokeLater(task); return task.get(); } } }