Swing、被动视图和长时间运行的任务

发布于 2024-08-23 05:58:30 字数 1632 浏览 6 评论 0原文

我正在尝试实现 被动 View 基于 GUI 系统摇摆中。基本上我想保持我的视图实现(实际包含 swing 代码的部分)最少,并在我的 Presenter 类中完成大部分工作。 Presenter 不应该依赖于 swing,并且还应该“运行节目”,即告诉视图要做什么,而不是相反。

在处理长时间运行的任务和一般的线程分离时,我遇到了问题。我希望 GUI 更新在 EDT 上运行,而演示者逻辑在不同的线程上运行。如果我希望演示者更新 GUI 的某些部分,这非常简单,我会写这样的内容:

public interface View {
    void setText(String text);
}

public class Presenter {
    View view;
    ...
    public void setTextInVIew() {
        view.setText("abc");
    }
}

public class MyFrame implements View {
    JTextField textField;
    ...
    public void setText(final String text) {
        SwingUtilities.InvokeLater(new Runnable() {
            public void run() {
                textField.setText(text);
            }
        });
    }
}

但是,当 GUI 通知演示者发生了某些操作时,我想切换出 EDT 来对此做出反应在不同的线程中:

public class Presenter {
    ...
    public void buttonPressed() {
         // shouldn't run on EDT
    }
}

public class MyFrame implements View {
    JButton button;
    public MyFrame() {
        ...
        button.addActionListener(new ActionListener() {
            @Override public void actionPerformed(ActionEvent e) {
                presenter.ButtonPressed();
            }
        });
    }
}

由于 actionPerformed 代码是从 EDT 运行的,因此 Presenter.buttonPressed 也将运行。我知道 swing 有 SwingWorker 的概念 - 在不同的线程中运行任务,但是看起来我必须将 swing 代码插入到我的演示器中,并且视图正在运行节目。有什么想法如何解决这个问题吗?

I'm trying to implement a Passive View based gui system in swing. Basically i want to keep my view implementation (the part that actually contains swing code) minimal, and do most of the work in my Presenter class. the Presenter should have no dependency on swing and also should "run the show", i.e. tell the view what to do and not vice versa.

I run into problems when dealing with long running tasks, and threads separation in general. I want GUI updates to run on the EDT, and the presenter logic to run on a different thread. If i want the presenter to update some part of the GUI it's pretty easy, i write something like that:

public interface View {
    void setText(String text);
}

public class Presenter {
    View view;
    ...
    public void setTextInVIew() {
        view.setText("abc");
    }
}

public class MyFrame implements View {
    JTextField textField;
    ...
    public void setText(final String text) {
        SwingUtilities.InvokeLater(new Runnable() {
            public void run() {
                textField.setText(text);
            }
        });
    }
}

However, when the GUI is to inform the presenter that some action has occurred, i want to switch out of the EDT in react to it in a different thread:

public class Presenter {
    ...
    public void buttonPressed() {
         // shouldn't run on EDT
    }
}

public class MyFrame implements View {
    JButton button;
    public MyFrame() {
        ...
        button.addActionListener(new ActionListener() {
            @Override public void actionPerformed(ActionEvent e) {
                presenter.ButtonPressed();
            }
        });
    }
}

since the actionPerformed code is running from the EDT, so will the presenter.buttonPressed. I know swing has the concept of SwingWorker - running tasks in a different thread, however it looks like i'll have to insert swing code into my presenter, and the view is running the show. Any ideas how to solve this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

寂寞清仓 2024-08-30 05:58:31

其他人概述的 SwingWorker 解决方案的另一种方法是使用具有线程关联性的事件总线。实际上,我认为这可能是您想要的解耦类型的最佳选择。

查看:EventBus

总线架构还有其他实现,但 EventBus 很流行。

-- 更新 --

所以 EventBus 将提供一种非常干净的从非 EDT 代理到 EDT 的方法(比对 SwingUtilities.invokeLater() 的大量显式调用要好得多 - 但基本上做同样的事情。虽然 EventBus 是能够捆绑许多通知并让它们在单个 EDT 可运行中命中,因此性能会更好)。

但这并不能解决从 EDT 代理事件并使它们在工作线程上运行的需要。 EventBus 中有一个 ThreadSafeEventService 类,它可能可以用作此类野兽的基础 - 例如,它可以与 ExecutorService 结合使用,以处理某些侦听器的某些事件注册。

我想对我来说这一切的关键是,无论你想出什么解决方案,它都应该尝试封装 EDT 的旋转开/关

BTW - 你在这里问的问题与微软的公寓线程模型非常相似。

Another approach to the SwingWorker solution outlined by others is to use an event bus with thread affinity. I actually think that this might be the best option for the type of decoupling you are going for.

Check out: EventBus

there are other implementations of the bus architecture, but EventBus is popular.

-- update --

So EventBus is going to provide a very clean way of proxying from non-EDT to EDT (a lot nicer than tons of explicit calls to SwingUtilities.invokeLater() - but basically doing the same thing. Although an EventBus is able to bundle up many notifications and have them hit in a single EDT runnable, so performance will be better).

But this doesn't address the need to proxy events from the EDT and get them running on a worker thread. There is a ThreadSafeEventService class in EventBus that could probably be used as the foundation for such a beast - it could be coupled with an ExecutorService, for example, to process certain event registrations for certain listeners.

I guess the key in all this for me is that whatever solution you come up with, it should try to encapsulate the spinning on/off the EDT

BTW - What you are asking about here is really similar to Microsoft's Apartment threading model.

遥远的绿洲 2024-08-30 05:58:31

好的 - 我为您提供了另一个选择: Spin

最终,所有这些解决方案都是代理线程之间的调用。我认为我们的目标是找到一个不需要大量样板代码的解决方案。例如,您可以连接所有侦听器,以便它们检查是否位于适当的工作线程上,如果不是,则代理到 ExecutorService。但这是一个大麻烦。更好的是让代理发生在业务和视图对象之间的层中 - 绑定/侦听器/无论您想如何称呼它层。

OK - I've got another option for you: Spin

Ultimately, all of these solutions are proxying calls between threads. I think the goal is to find a solution that doesn't require gobs of boilerplate code on your end. You could, for example, wire all of your listeners so they check to see if they are on an appropriate worker thread, then proxy to an ExecutorService if not. But that's a major hassle. Much better to get that proxying occuring in the layer between your business and view objects - the binding/listener/whatever you want to call it layer.

梦途 2024-08-30 05:58:30

您可以执行类似以下操作,这将使您的 GUI 代码保持在适当的位置,并简单地执行退出 EDT 的工作:

 button.addActionListener(new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
           SwingWorker sw = new SwingWorker() {
             public Object doInBackground(){
                 presenter.ButtonPressed();             
                 return null;
            }
          }; 
          sw.execute();
        }
    });

you could do something like the following, which will keep your GUI code in place and simply perform the work to get out of the EDT:

 button.addActionListener(new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
           SwingWorker sw = new SwingWorker() {
             public Object doInBackground(){
                 presenter.ButtonPressed();             
                 return null;
            }
          }; 
          sw.execute();
        }
    });
为人所爱 2024-08-30 05:58:30

您可能对 Task API 感兴趣,以避免所有样板文件。否则 akf 的解决方案看起来不错(尽管不需要为 SwingWorker 创建变量,您只需新建并执行一个匿名变量即可)。

You might be interested in the Task API to avoid all the boilerplate. Otherwise akf's solution looks fine (although no need to create a variable for SwingWorker, you can just new and execute an anonymous one).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文