在Swing的EDT中实现线程化?

时间:2013-02-05 21:08:10

标签: java multithreading swing swingworker

我的项目是基于Java的Swing库构建的。它产生了显示我的GUI(正常工作)的EDT。

程序的入口,初始化EDT:

public final class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Start());
    }

    class Start implements Runnable {
        private Model model = new Model();
        private Controller controller = new Controller(model);
        private View view = new View(controller);

        @Override
        public void run() {
            // Initialize the view and display its JFrame...
        }
    }
}

}

但是,当在我的GUI中单击按钮/单选框/等时,Controller类必须对模型执行操作。

我的问题如下:

  • 我应该将控制器的代码包装在新的SwingWorker中吗?
    • 如果不是,我应该将模型的代码包装在新的SwingWorker中吗?
  • 如果我用线程包装控制器的代码,我是否需要在模型中同步共享状态变量?
  • 如果我的模型在新线程上运行,通知我的GUI更改,这会发生在EDT还是新线程上?

例如:

public class Controller {
    public void updateModel() {
        new SwingWorker<Void, Void>() {
            @Override
            protected Void doInBackground() throws Exception {
                model.somethingSomethingSomething();
            }
        }.execute();
    }
}

public class Model {
    public void somethingSomethingSomething() {
        notifyListeners(); // This is going to notify whichever GUI 
                           // is listening to the model.
                           // Does it have to be wrapped with Swing.invokeLater?
    }
}

public class View {
    // This function is called when the model notifies its listeners.
    public void modelChangedNotifier() {
        button.setText("THE MODEL HAS CHANGED"); // Does this occur on the EDT?
    }
}

3 个答案:

答案 0 :(得分:4)

您可以在此处阅读:Improve Application Performance With SwingWorker in Java SE 6。简而言之:所有耗时的操作,不受影响的UI必须在另一个线程中完成。要显示操作结果,您必须返回EDT。例如,如果进行数据库搜索,则应显示进度条(通常为无限)并使用SwingWorker开始搜索。要在表格中显示搜索结果,您必须在EDT中。或者,您可以使用foxtrot lib(它允许您的代码更方便Swing而无需重新设计)。 如果您的控制器代码永久更新swing小部件,您应该在EDT中执行它,或者至少在EDT中执行UI的这些更新(使用SwingUtilities.invokeLater,SwingWorker中的块处理或swing.Timer)。 所以你的样本是错误的:模型更新应该在EDT中是最新的。

答案 1 :(得分:4)

而不是从doInBackground()publish()中期结果更新您的模型,而是从在EDT上执行的process()更新您的模型。在此example中,JTable对应于您的View,而TableModel对应于您的Model。请注意,JTable会收听自己的TableModel

答案 2 :(得分:-2)

一种替代方法,来自实践9.4.2中的Java Concurrency,使用“Split”或“Shared Data Model”。您可以在任何您想要的线程上更新您的业务模型,可能是长期运行的非EDT线程。但是,不要直接调用notifyListeners()并担心你所在的线程,而只需调用myComponent.repaint(),它将在EDT 上排队重绘请求

然后,在paintComponent()方法的某处,您明确地从模型中获取所有新数据,通常是在名为modelToView()

的方法中
   commentTextArea.setText(myModel.getCommentText());
   fooLabel.setText(myModel.getFooText());
   ...

好处是线程不是一个问题,至少对某些人来说,这“有道理”,而且模型与视图很好地分离。缺点是您每次都要重置所有值。所以,如果你有100个JComponents,那就是设置的100件事。此外,视图与模型紧密耦合。


工作代码示例

@MadProgrammer和@kleopatra是正确的,如果视图直接包含正在更新的组件,则会出现“无限循环的厄运”。有关证明,请参阅

Demo_14716901_Fails

但是,如果视图与组件隔离,则可以避免无限循环。通常情况下,更高级别的视图将包含JSplitPanes,持有JScrollPanes,持有Box或更多JPanels等内容,保留实际的低级别组件。所以这个要求,IMO,并非不合理。

Demo_14716901_Works

的工作代码

为Downvoters添加了一些评论

有些人想要击败Swing 。他们正在编写仪器控制代码或算法,只想在不担心EDT,无休止的SwingWorkers和invokeLaters的情况下完成工作。这项技术让他们完成了自己的工作。有一个重要的警告,它有效。 (就个人而言,我理解并且通常喜欢Swing,但很多人不喜欢)。

虽然Swing组件很适合MVC,但它们通常都处于微观层面。 “真实”模型不是单个String,而是几十个值。 “真正的”视图不是单个JLabel,它是许多JPanel,每个都有许多组件,结合了滚动器,分离器等。这种技术通常更适合现实世界,允许程序员自然地在更高层次上思考。 / p>

就“糟糕的做法”而言,请与Brian Goetz,Josh Bloch等人联系,这是“对权威的吸引力”,但它对我有用。 : - )