简体   繁体   English

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

[英]SwingUtilites: how to return values from another thread in Java?

I am trying to make an application in Java. 我正在尝试用Java创建一个应用程序。 To make Swing work correctly, I did this: 为了使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?
}

How would I get this input string in my main body? 我如何在我的主体中获得此输入字符串?

You can use AtomicReference<String> for passing values between threads in a thread-safe manner. 您可以使用AtomicReference<String>以线程安全的方式在线程之间传递值。

As noted by Hemal, you'll need some synchronization between two threads to make sure it was already executed. 正如Hemal所说,你需要在两个线程之间进行一些同步,以确保它已经被执行。 For example, you can use CountDownLatch or use SwingUtilities.invokeAndWait (make sure you don't call it from Swing thread!) 例如,您可以使用CountDownLatch或使用SwingUtilities.invokeAndWait(确保您不要从Swing线程调用它!)

Update: here is the complete example using AtomicReference and CountDownLatch 更新:这是使用AtomicReferenceCountDownLatch的完整示例

public class Main {
    public static void main(String[] args) throws InterruptedException {
        final AtomicReference<String> result = new AtomicReference<String>();
        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());
    }
}

Also read this answer about general design of GUI (and Swing) applications. 另请阅读有关GUI(和Swing)应用程序的一般设计的答案

How would I get this input string in my main body? 我如何在我的主体中获得此输入字符串?

You wouldn't. 你不会。 The idea that your "main" would invoke a Swing dialog box and then do something with the results is contrary to the entire idea of a graphical user interface. 您的“main”将调用Swing对话框然后对结果执行某些操作的想法与图形用户界面的整个想法相反。

In a GUI, you design your program to deal with a series of user-initiated events. 在GUI中,您可以设计程序来处理一系列用户启动的事件。 Those events may be completely asynchronous, such as the keystrokes, selection, and menu choices of your typical word processor. 这些事件可能是完全异步的,例如典型文字处理器的击键,选择和菜单选择。 Or they may be scripted, such as the question-answer format of a "wizard." 或者它们可能是脚本化的,例如“向导”的问答形式。

Assuming that you want to do something like the latter, you would implement it using the following sequence: 假设您想要执行后者之类的操作,您可以使用以下顺序实现它:

  1. The user initiates some action, perhaps selecting a menu choice. 用户启动一些操作,可能选择菜单选项。 This is turned into an invocation of an ActionListener , which decides that it needs more input from the user. 这转变为ActionListener的调用,它决定它需要来自用户的更多输入。
  2. The ActionListener , which is executed on the event dispatch thread, is permitted to do anything that it wants to the UI, such as displaying a dialog. ActionListener在事件派发线程上执行,允许对UI执行任何操作,例如显示对话框。 That dialog may be modal or non-modal; 该对话可以是模态的或非模态的; in one case the output is available to the original listener, in the other you need to write a new listener to take subsequent action. 在一种情况下,输出可用于原始侦听器,而另一种情况则需要编写新的侦听器以执行后续操作。
  3. Once you have enough information, you may choose to invoke a background operation. 获得足够的信息后,您可以选择调用后台操作。 You would typically have a thread-pool to service these requests. 您通常会有一个线程池来为这些请求提供服务。 You would not attempt to perform the request on the "main" thread; 您不会尝试在“主”线程上执行请求; in fact, for all intents the main thread is no longer running. 事实上,对于所有意图,主线程不再运行。
  4. When your operation completes running, it would push data back to the event dispatch thread using SwingUtilities.invokeLater() . 当您的操作完成运行时,它将使用SwingUtilities.invokeLater()将数据推送回事件派发线程。 While you could use invokeAndWait() to send results to Swing in the middle of your background operation, that's rarely a good idea. 虽然您可以使用invokeAndWait()在后台操作过程中将结果发送到Swing,但这不是一个好主意。 Instead, create a sequence of operations, preferably one that is easily canceled by the user. 而是创建一系列操作,最好是一个易于被用户取消的操作。

The "standard" way to initiate operations on a background thread is via SwingWorker . 在后台线程上启动操作的“标准”方法是通过SwingWorker There are alternatives; 还有其他选择; for example, you could use a BlockingQueue to send operations to a single long-running background thread, and use invokeLater() to return the results. 例如,您可以使用BlockingQueue将操作发送到单个长时间运行的后台线程,并使用invokeLater()返回结果。

Regardless, there's one rule that you do not want to break: never, ever, perform a blocking operation on the event dispatch thread . 无论如何,有你想要打破一个规则: 永远,永远,事件调度线程上执行阻塞操作 If you do that, then your application is broken . 如果你这样做,那么你的申请就会被破坏

Right now you have two threads going: the main thread and the EDT (event dispatch thread). 现在你有两个线程:主线程和EDT(事件调度线程)。 I assume you know that SwingUtilities.invokeLater(runnable) is running a task on the EDT. 我假设您知道SwingUtilities.invokeLater(runnable)正在EDT上运行任务。

To share data between threads, you just need some variable that is in the scope of both threads. 要在线程之间共享数据,您只需要一些在两个线程范围内的变量。 The easiest way to accomplish that is to declare a volatile data member or AtomicReference in the class containing the main method. 最简单的方法是在包含main方法的类中声明volatile数据成员或AtomicReference

In order to ensure that you read the value after it is returned by the JOptionPane , the simplest thing you can do here is to change the invokeLater call to an invokeAndWait call. 为了确保在JOptionPane返回后读取值 ,这里最简单的方法是将invokeLater调用更改为invokeAndWait调用。 This will cause your main thread to stop executing until what you have put onto the EDT has completed. 这将导致主线程停止执行,直到您放入EDT的内容完成为止。

Ex: 例如:

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

If your main thread is executing some task that shouldn't be stopped while the option pane is present, then in the main thread you can periodically check (ie, in the outer part of the loop that is running your task) whether or not mySharedData has been set. 如果你的主线程正在执行一些在选项窗格存在时不应该停止的任务,那么在主线程中你可以定期检查(即,在运行你的任务的循环的外部)是否mySharedData已经设置好了。 If your task doesn't loop and is instead doing some I/O or waiting, you can make use of Thread.interrupt and check mySharedData in the InterruptedExecption handlers. 如果你的任务没有循环而是做一些I / O或等待,你可以使用Thread.interrupt并在InterruptedExecption处理程序中检查mySharedData

You can use an AtomicReference and invokeAndWait. 您可以使用AtomicReference和invokeAndWait。

public static void main(String[] array){ 

AtomicReference<String> outerInput = new AtomicReference<String>();
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.
}

I suggest using the observer/observable pattern for this, perhaps with a PropertyChangeListener. 我建议使用观察者/可观察模式,也许使用PropertyChangeListener。 Then your Swing app will be able to notify any and all listeners if the critical variable(s) state changes. 然后,如果关键变量状态发生变化,您的Swing应用程序将能够通知任何和所有侦听器。

For example: 例如:

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

}

The following code will do what you want. 以下代码将执行您想要的操作。 I have done something similar except I was launching a JFileChooser instead of an input dialog. 我做了类似的事情,除了我正在启动JFileChooser而不是输入对话框。 I found it more convenient than hard coding a bunch of paths into my application or accepting a command line argument, at least for testing purposes. 我发现它比将一堆路径硬编码到我的应用程序或接受命令行参数更方便,至少为了测试目的。 I would like to add that one could modify the prompt() method to return the FutureTask instance for added flexibility. 我想补充一点,可以修改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<String> {

        private final String message;

        public Prompt(String message) {
            this.message = message;
        }

        /**
         * This will be called from the Event Dispatch Thread a.k.a. the Swing
         * Thread.
         */
        @Override
        public String call() throws Exception {
            return JOptionPane.showInputDialog(message);
        }

        public String prompt() throws InterruptedException, ExecutionException {
            FutureTask<String> task = new FutureTask<>(this);
            SwingUtilities.invokeLater(task);
            return task.get();
        }

    }

}

You can trivially expose it to the outer class by declaring a String[] in which the runnable sets the value. 通过声明runnable设置值的String[] ,可以将它暴露给外部类。 But note that you will need some synchronization mechanism to know whether it has been assigned by the Runnable . 但请注意,您需要一些同步机制来了解它是否已由Runnable分配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM