从SwingUtilities.InvokeLater调用获取返回值?

时间:2014-09-03 02:45:00

标签: java swing

在我的java应用程序中,我在主JTabbedPane上有JFrameJPanel个标签。在标签内我有一个功能

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生成线程,我想将JPanel的引用传递给主JFrame,以便它可以调用其中的函数来告诉用户线程正在做什么。

3 个答案:

答案 0 :(得分:6)

看起来您的应用程序数据都绑定在用户界面对象中。如果您所做的一切都可以在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是一个 一个Runnable和一个Future的界面。这很有用,因为invokeLater只需Runnable,但Future可以返回一个值。更具体地说,Future可以捕获在一个线程中返回的值并将其存储,直到另一个线程想要获取它。 FutureTask是一个方便的,现成的RunnableFuture实现,它带有一个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数据结构分离,这种技术应该非常有效。

答案 1 :(得分:0)

这对我有效 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);
        }

答案 2 :(得分:0)

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

您将需要此类来定义一个最终对象,该对象封装您的返回值:

public class Shell<T> {

    private T value;

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

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

随后,您可以运行整个嵌套嵌套Runnable文件。像这样:

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(),因为如果您在null中执行的操作未分配a,则可能返回值为Runnable。值Shell对象。如果您不希望线程被阻塞并使用invokeLater(),则还可以返回Shell对象以在其具有值时读取它。