【问题标题】:Getting return values from SwingUtilities.InvokeLater calls?从 SwingUtilities.InvokeLater 调用中获取返回值?
【发布时间】:2014-10-27 10:18:11
【问题描述】:

在我的 java 应用程序中,我在主 JFrame 上有一个 JTabbedPane,并带有许多 JPanel 选项卡。在标签中我有一个功能

public void newStatus(final String arg)
{
    Runnable sendMsg = new Runnable() 
    {
        @Override
        public void run() 
        {
           mainView.newStatusLine(arg);
        }
    };
    SwingUtilities.invokeLater(sendMsg);
}

此函数调用主要的JFrame mainView 函数将一些文本写入JTextPane。我的问题是这不允许我从主要的JFrame 获得返回值。我想做类似的事情

public InfoObj getInfo()
{
    Runnable sendMsg = new Runnable() 
    {
        @Override
        public void run() 
        {
           return mainView.getInfo();
        }
    };
    SwingUtilities.invokeLater(sendMsg);
}

但我不确定如何进行这项工作。我已经尝试并按照我的 IDE 的消息来查看是否可以让它工作,但我不能覆盖 Runnable.Run 来返回一些东西。

有什么机制可以做这样的事情吗?

编辑:对于 HoverCraftFullOfEels,整体问题在于 JPanels 本身之间以及主要 JFrameJPanel 之间的对话。在某些时候,JPanel 想告诉主要的JFrame 做某事,或者它可能想从中获取一些数据。但据我所知,我不能只将this 的引用传递给JFrameJPanel 并用它来调用公共函数或读取其中的一些公共字段。

有时我不想通过衍生线程在 EDT 上执行此操作。一些JPanel 产生线程,我想将JPanels 引用传递给主JFrame,以便它可以调用其中的函数来告诉用户线程正在做什么。

【问题讨论】:

  • 这闻起来可能是 XY 问题。更好地告诉我们您要解决的总体问题,而不是您尝试使用代码解决问题的方式。另外,为什么还要在这里在 EDT 上排队 Runnable?我认为这段代码已经被 EDT 调用了,不是吗?
  • 稍后的哪一部分你不明白?该代码尚未运行。
  • 一般是返回你需要的东西Callable.
  • 基本上,你不能......你能做的就是设置某种observer pattern
  • @PM77-1:但如果它在事件线程上排队,那将不起作用。然后它将表现为 Runnable。

标签: java swing


【解决方案1】:

看起来您的应用程序数据都绑定在您的用户界面对象中。如果您所做的一切都可以在 EDT 上完成,那么您应该没问题。您可以在需要彼此信息的对象之间进行直接调用。由于所有这些都在 EDT 上,因此它实际上是单线程的,并且没有竞争条件或死锁。然而,这可能会将 UI 代码的各个部分相互耦合得过于紧密。在这种情况下,您可以按照评论者的建议使用某种观察者或侦听器模式。这在单线程、事件驱动的环境(如 AWT/Swing)中运行良好。

但是您说您可能希望从 EDT 以外的某个线程执行此操作。这改变了一切。

(顺便说一句,这种情况会导致人们认为将所有应用程序数据绑定在 UI 对象中是一种反模式。这使得从外部获取这些数据变得非常困难。 UI 子系统。)

可以添加观察者模式,但有一些限制。 UI 拥有数据,因此只有 EDT 可以更改数据并向观察者触发事件。观察者列表需要以线程安全的方式维护。据推测,观察者会想要更新数据结构以响应事件。这些数据结构必须是线程安全的,因为它们既可以从 EDT 也可以从您的其他线程访问。这种方法可行,但您必须仔细考虑哪个线程正在调用哪些方法,以及必须使哪些数据结构成为线程安全的。

假设你不想走这条路,让我们回到你原来的问题,关于如何从invokeLater返回一些东西。这样做可以让您将数据保留在 UI 中,同时允许其他线程在需要时从 UI 中获取数据。这可以做到这一点,有点间接。不过,它确实有风险。

这是代码。这可以从“其他”(非 EDT)线程调用。

InfoObj getInfo() {
    RunnableFuture<InfoObj> rf = new FutureTask<>(() -> getInfoObjOnEDT());
    SwingUtilities.invokeLater(rf);
    try {
        return rf.get();
    } catch (InterruptedException|ExecutionException ex) {
        ex.printStackTrace(); // really, you can do better than this
    }
}

这里的诀窍是RunnableFuture 是一个RunnableFuture 的接口。这很有用,因为invokeLater 只接受Runnable,但Future 可以返回一个值。更具体地说,Future 可以捕获一个线程中返回的值并将其存储,直到另一个线程想要获取它。 FutureTaskRunnableFuture 的方便、现成的实现,它接受Callable 参数,一个可以返回值的函数。

基本上,我们创建一个FutureTask 并交给它一些将在 EDT 上运行的代码(Callable,本示例中的 lambda 表达式)。这将收集我们想要的信息并将其返回。然后我们使用invokeLater 将其发布到 EDT。当其他线程想要数据时,它可以立即调用get(),也可以挂在Future 上,稍后再调用get()Future.get() 会抛出一些您必须处理的已检查异常,这带来了一点不便。

这是如何工作的?如果 EDT 首先运行Callable,则返回值存储在Future 中,直到另一个线程在其上调用get()。如果其他线程首先调用get(),它会被阻塞,直到值可用。

还有问题。精明的读者会认识到这与invokeAndWait() 具有相同的风险,即如果您不小心,可能会导致系统死锁。这可能是因为调用get() 的线程可能会阻塞等待EDT 处理invokeLater 发布的事件。如果 EDT 由于某种原因被阻塞——可能是在等待另一个线程持有的东西——结果就是死锁。避免这种情况的方法是在调用可能阻塞等待 EDT 的东西时要格外小心。一个好的一般规则是在调用这些阻塞方法之一时不要持有任何锁。

有关如何使用invokeAndWaitinvokeLater(FutureTask) 让自己陷入困境的示例,请参阅this questionmy answer

如果您的其他线程与 UI 数据结构完全解耦,那么这种技术应该会非常有效。

【讨论】:

  • 我想我的问题的答案就在这里。但它不适用于某些现实世界,我无法对其进行正确审查。我在这里stackoverflow.com/questions/25649950/… 提出了类似的问题,您是否愿意为我复习?
【解决方案2】:

我知道这个问题已经很老了,但是下面应该为这个问题提供一个方便的解决方法。

你需要这个类来定义一个封装你的返回值的最终对象:

public class Shell<T> {

    private T value;

    public T getValue() {
        return this.value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

之后,您可以运行整个嵌套的Runnable f.e。像这样:

public InfoObj getInformation() {
    final Shell<InfoObj> returnObj = new Shell<InfoObj>();
    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                returnObj.setValue(mainView.getInfo());
            }
        });
    } catch (InvocationTargetException | InterruptedException e1) {
        e1.printStackTrace();
    }
    return returnVal.getValue();
}

请注意,我更改了问题中的方法名称,因为此方法不打算从 Runnable 中调用。您可以从任何线程调用此方法,它会返回一个值。但是,我也将invokeLater() 更改为invokeAndWait(),因为如果您在Runnable 中执行的操作尚未为Shell 对象分配值,则返回值可能是null .如果您不希望您的线程被阻塞并使用invokeLater(),您还可以返回Shell 对象以在它有值时读取它。

【讨论】:

    【解决方案3】:

    这对我有用 JAVA7

     //Here the main thread invokes it
        final RunnableFuture<Data> task = new FutureTask<Data>( new Callable<Data>() {
            @Override
            public Data call() throws Exception {
                      //Here EDT thread
                    return calculateData();
               }
            });
            SwingUtilities.invokeLater(task);
            try {
            return task.get();
            } catch (InterruptedException | ExecutionException e) {
                logger.log(Level.SEVERE, "Exception", e);
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-28
      • 1970-01-01
      • 1970-01-01
      • 2014-05-28
      • 2019-10-14
      • 2019-05-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多