【问题标题】:Swing, Passive View and Long running tasksSwing、被动视图和长时间运行的任务
【发布时间】:2010-02-26 13:09:06
【问题描述】:

我正在尝试在 swing 中实现基于 Passive View 的 gui 系统。基本上我想让我的视图实现(实际上包含摆动代码的部分)最小化,并在我的 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 代码插入到我的演示者中,并且视图正在运行节目。任何想法如何解决这个问题?

【问题讨论】:

    标签: java multithreading design-patterns swing passive-view


    【解决方案1】:

    您可以执行以下操作,这将保留您的 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();
            }
        });
    

    【讨论】:

    • 我使用与上述类似的模式,并且 SwingWorker 线程按预期工作。 +1
    • 作为一种优化,我建议使用执行器而不是为永远摇摆的工人调用产生一个新线程。
    【解决方案2】:

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

    【讨论】:

    • 您的 doInBackground() 方法需要返回 Void(不是 void),因此您需要在调用 presenter.ButtonPressed() 后返回一些内容(例如 null)。
    • JRL:在调用 .execute() 而不是将 SwingWorker 分配给局部变量时,您仍然比 akf 短一行。
    【解决方案3】:

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

    查看:EventBus

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

    -- 更新--

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

    但这并没有解决从 EDT 代理事件并让它们在工作线程上运行的需求。 EventBus 中有一个 ThreadSafeEventService 类,它可能被用作这种野兽的基础——它可以与 ExecutorService 耦合,例如,为某些侦听器处理某些事件注册。

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

    顺便说一句 - 您在这里询问的内容与 Microsoft 的 Apartment 线程模型非常相似。

    【讨论】:

    • 你是否必须明确地做一些事情来获得这个线程亲和力?我按照EventBus入门代码(Frame在按钮单击时发布changeEvent,Presenter订阅此事件),但是Presenter中事件的处理仍然在EDT上完成
    • 这是一个很好的观点。 EventBus 只做一种方式代理(如果事件源自 EDT,它将自动代理到 EDT)。听起来您需要它在两个方向上工作(源自 EDT 的事件需要传递给 EDT 之外的听众)。我将更新帖子以对此进行一些扩展。
    【解决方案4】:

    好的 - 我有另一个选择:Spin

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-06
      • 1970-01-01
      • 2012-08-13
      • 2022-01-01
      • 1970-01-01
      相关资源
      最近更新 更多