【问题标题】:Is it OK to call Future.get() on a non-EDT thread from the EDT thread?从 EDT 线程调用非 EDT 线程上的 Future.get() 可以吗?
【发布时间】:2016-01-21 15:57:29
【问题描述】:

这是在单元测试的上下文中(当它发生时)。

在测试结束时,无论结果如何,我都希望代码检查JFileChooser 对话框的存在(可见性)...如果可见,则将其关闭。

当然,消除对话有不同的方式,但为了模仿人类行为(并在此举一个我关心的问题的例子),我选择使用java.awt.Robot。后者的方法应该在非 EDT 线程中运行。

实际上我已经扩展了 Robot 以包含一个名为type_input 的便捷方法,所以

robot.type_input( KeyEvent.VK_F4, KeyEvent.VK_ALT )

先按 Alt,然后 F4,然后释放 F4,然后 Alt:就像人类关闭窗口/对话框一样。

我使用invokeAndWait 提交Runnable,因为我不希望代码在此对话框被关闭之前运行到下一个测试。我必须在 EDT 中测试可见性和焦点。但正如我所说,Robot 方法必须在非 EDT 中运行。

在 EDT 中像这样使用 get() 是否有任何潜在问题?它是否可能导致 GUI 无响应?问题是,我听说该框架能够在某些条件下“启动一个新的 EDT 泵”。我不得不承认,这是我觉得自己最不了解的与 EDT 相关的问题之一......

import java.awt.EventQueue;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;

class MainFrame extends JFrame {
    JFileChooser update_target_file_chooser;
    JDialog file_chooser_dlg;

    // ... rest of class

}

public class ThreadWithinThread {

    public static void main(String[] args) throws InvocationTargetException, InterruptedException {

        final ExecutorService thread_pool_exec_serv = Executors.newFixedThreadPool( 5 );

        class DismissDlg implements Runnable {
            MainFrame main_frame;
            Robot robot;

            @Override
            public void run() {
                boolean focus_on_dlg = main_frame.file_chooser_dlg.hasFocus();
                if( main_frame.file_chooser_dlg.isVisible() ){
                    if( ! focus_on_dlg ){
                        main_frame.file_chooser_dlg.requestFocus();
                    }
                    class AltF4 implements Callable<Void>{
                        public Void call(){
                            return robot.type_input( KeyEvent.VK_F4, KeyEvent.VK_ALT );
                        }
                    }
                    Future<Void> future_result = thread_pool_exec_serv.submit( new AltF4() );
                    try {
                        // this is the line I'm worried about
                        future_result.get();
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        EventQueue.invokeAndWait( new DismissDlg() );
    }

}

稍后

正如我在回复中所说,这不是针对手头的案例提供一个特别实用的解决方案:我真的想了解另一个 EDT“事件泵”是否以及何时以 Future.get() 阻止 EDT 启动. 正如我所说,我发现这整个方面很难理解。我使用JOptionPane 的静态方法(必须在 EDT 中运行,请参阅 SO ad 上的帖子)向我证明,这些方法(确实会阻止 EDT!)实际上似乎并没有阻止 GUI功能正常(注意不要与这些 JOptionPanes 或它们的 JDialogs 是模态的还是非模态的混淆)。

我的理解是,这是因为“框架”随后启动了另一个“事件泵”。那么这里可以吗?

【问题讨论】:

  • 获取对 JFileChooser 的引用并调用 isVisible,然后调用 setVisible(false) 有多难?
  • @Bill 谢谢 - 事实上,我主要关心的是使用 Future.get() 是否会导致另一个“事件泵”在另一个 EDT 事件发生时开始。这并不是说我在寻找“一些解决方案”时遇到了实际困难。
  • 只是一个想法。可能是您无法访问该对话框,但可能是您可以。有时我们会陷入更复杂的答案,以至于忘记了简单的东西。

标签: java swing concurrency edt


【解决方案1】:

所以,来自JavaDocs

如有必要,等待计算完成,然后检索其结果。

这表明该方法是一个阻塞方法,如果在 Event Dispatching Thread 的上下文中你不应该做一件事,它会在 Event Dispatching Thread 的上下文中调用阻塞方法

啊,但是怎么办呢?您可以改用SwingWorker,它在内部使用它自己的ExecutorService,或submit 工作人员到您自己的ExecutorServiceSwingWorker 实现Runnable),然后您应该能够使用@987654329 @ 监视SwingWorker 的状态,当它是DONE 时,您可以从它的get 方法中检索值而不会阻塞

【讨论】:

  • 谢谢...是的,我对 SwingWorker 了如指掌,并且一直在使用它。我的问题的重点是了解如果在 get() 阻塞 EDT 时生成另一个 EDT 事件,是否在此处使用 Future.get() 会导致另一个“事件泵”启动...
  • 不确定我理解你的意思,Future#get 将阻塞直到Callable 返回结果或Exception,这意味着如果从 EDT 的上下文中调用,你将阻止它从处理任何新事件直到它被解除阻塞并且方法返回
  • “正如我所说,我发现这整个方面很难理解。我使用 JOptionPane 的静态方法......向我证明了这些方法(确实会阻止 EDT!)不实际上似乎阻止了 GUI 运行” - 模态对话框通过接管事件分派过程来工作,允许处理和分派事件,但允许它们“阻止”当前线程的执行......是的,我的头疼到
  • 谢谢!是的,你可以把“block”放在引号中,因为我们确实在谈论 EDT……我不知道模态对话框是如何工作的……所以它们或多或少是整个 Swing 中的一个独特功能/AWT 架构?你知道我在哪里可以读到这个吗?
  • 我挖掘了 API 源代码,但你可能想看看 Foxtrot 这是一个 API,它做同样的事情,但允许你调用其他代码段
【解决方案2】:

在 EDT 上执行必要的步骤,并通知测试线程是否需要额外的步骤:

class PrepDlgDismiss implements Runnable {
  boolean file_chooser_visible;
  @Override
  public void run() {
    boolean focus_on_dlg = main_frame.file_chooser_dlg.hasFocus();
    if( main_frame.file_chooser_dlg.isVisible() ){
      file_chooser_visible = true;
      if( ! focus_on_dlg ){
        main_frame.file_chooser_dlg.requestFocus();
      }
    }
  }
}
PrepDlgDismiss task = new PrepDlgDismiss();
EventQueue.invokeAndWait( task );
if( task.file_chooser_visible ){
  robot.type_input( KeyEvent.VK_F4, KeyEvent.VK_ALT );
}

【讨论】:

  • 谢谢...这是一个“重构”问题的实用答案,尽管我会说从并发 PoV 中“存储”这样的可见性状态并不是非常可取的。但我的问题的重点是了解如果在 get() 阻塞 EDT 时生成另一个 EDT 事件,是否在此处使用 Future.get() 会导致另一个“事件泵”启动......
  • @mikerodent 您对并发 POV 的可见性状态有何担忧?虽然没有权威记录,但invokeAndWait() 执行的任务中的操作与invokeAndWait() 的返回之间存在happens-before 关系。即使有了这个事实上的保证,你还觉得不舒服吗?如果是这种情况,您绝对不应该依赖更不可靠的行为,例如在 EDT 被阻塞时启动新的事件泵。
  • 事实上,最重要的是我想了解是否事件泵在我所描述的圈子中发挥作用:我不依赖它.. .我想明白!我在这里关于“可见”状态的观点是真正愚蠢的,在invokeAndWait 结束和robot.type_input 命令运行之间,其他一些代码可能会关闭对话框......并且这个 Alt-F4 机器人打字可能(例如)然后导致其他东西关闭......这对我来说是纯粹的并发有限性。
  • @mikerodent 只要使用Robot 将输入注入底层系统的队列,就会发生关闭错误窗口的情况;您的初始版本会遇到同样的问题。即使Robot 在 EDT 上运行,应用程序外部的活动也可能异步更改焦点。
  • 至于您的“真实”问题,这可能取决于您正在运行的 JVM,因为它是未指定的行为。如果您确定了特定的 JVM,也许查看源代码可以找到答案。
猜你喜欢
  • 2011-01-27
  • 1970-01-01
  • 2018-02-01
  • 2014-03-31
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
相关资源
最近更新 更多