【问题标题】:How do I give this CPU-intensive Java application a GUI?如何为这个 CPU 密集型 Java 应用程序提供 GUI?
【发布时间】:2010-12-14 07:26:00
【问题描述】:

我正在用 Java 编写一个小遗传算法,作为学校作业。到目前为止,我几乎一直坚持做控制台应用程序。但是我认为 UI 对这个程序非常有帮助,所以我想做一个。我无法弄清楚如何协调事件驱动的 GUI 和具有开头和结尾的控制台应用程序。

理想情况下,我希望有一堆用于设置的文本框,然后是一个开始按钮。一旦你点击开始,算法就会开始运行,并且 GUI 会在设定的时间间隔内更新最新的程序状态。在没有算法冻结 GUI 的情况下,我怎么能做到这一点,反之亦然?我不希望任何一方等待另一方。

如何让我的主循环在算法运行时不冻结 GUI?我认为它们需要在单独的线程中,但我以前从未弄乱过线程。对于这项任务来说,这似乎太复杂了,这一定是司空见惯的。

【问题讨论】:

  • 对任何可能觉得这很有用的人的更新:我最终使用了 SwingWorker。这很容易,而且效果很好。我强烈推荐给任何处于相同情况的人。

标签: java user-interface multithreading loops


【解决方案1】:

你正在做一些有线程的事情。在大多数情况下,GUI 编程都需要线程——幸运的是,Java 的线程 API 并不算太糟糕(Python 是基于它建模的,所以它在做一些正确的事情)。

不过,不要被线程吓倒——我会说它是中间的,但每个程序员都应该理解。

那里有很多信息会使您倾向于反对线程。然而,GUI 应用程序是它们非常有用的一个领域。线程的反对者会让你相信事件编程模型在这种情况下会帮助你,但实际上它不会。大多数说“线程很烂”的人提出的解决方案通常比线程本身更糟糕。

您可以尝试将您的解决方案整合到一个线程中,但这需要您的 CPU 密集型代码以可预测的时间间隔屈服于 GUI。那个解决方案很烂。 编辑: 由于其他人建议采用这种方法,让我详细说明它为什么糟糕:你不知道,GUI 中总是会更新一些东西。当您将一个窗口移到顶部然后后退时,该窗口下的整个区域都将失效,并且必须在 您的 进程中执行代码以重绘该部分。即使您非常快速地更新 GUI,这也会提供负面的用户体验,因为简单的 GUI 操作会完全阻塞。有时,当您将鼠标悬停时,按钮会突出显示。用户右键单击。所有这些事情都需要 CPU 时间来实现,如果您的单独线程正在咀嚼您的 GA,它们就不会发生。正在执行的 GUI 代码不仅是您的代码。

这似乎是very useful article on the topic。

关于该主题的两节课是:

  1. Concurrency
  2. Concurrency in Swing

【讨论】:

    【解决方案2】:

    当我在大学里为我的一门计算机图形课程编写光线追踪器时,我有一个长期运行的任务,我想在追踪器绘制时定期更新显示。我使用了两个单独的线程——一个线程休眠并更新(比如每 500 毫秒);另一个线程进行实际的光线追踪。关键是在一个公共对象上同步——在我的例子中,访问我的图像缓冲区是同步点(一个线程必须先等到另一个线程完成读取,才能对图像缓冲区进行更改)。

    对于您的 GA 处理,您可能有这样的内容(伪代码):

    假设您有一些对象 generationHistoryObject,它存储了您想要在 GUI 中显示的状态,那么:

    (在线程 #1 中:)

    Generation newGeneration = doMutationAndTestThisGeneration(lastGeneration);
    synchronized (generationHistoryObject) {
     generationHistoryObject.updateWithNextGeneration(newGeneration);
    }
    

    (在线程 #2 中:)

    while (!programIsDone()) {
     synchronized (generationHistoryObject) {
      drawGuiForCurrentState(generationHistoryObject);
     }
     Thread.sleep(500);
    }
    

    这个想法是您单独为每一代完成耗时的工作,然后在同步块中更新 GUI 必须访问的部分(让 GUI 等待绘制直到更新完成)。

    【讨论】:

    • 我可以只打“同步”,两个线程可以访问同一个对象?这似乎太容易了!
    • @Ross:在你深入研究线程之后——比如说,大约一年后——向我保证,你会回到 SO 并再次读给你自己的评论。
    • 几乎就是这么简单... synchronized(object) 将阻止两个线程进入在对象的同一实例上同步的块。如果你在多个资源上同步,你必须小心线程陷入死锁,但你可能不需要担心这里。我应该提一下,我的光线追踪器使用了 JOGL,它在一个 while 循环中进行渲染,并不是真正的事件驱动方式与 Swing 相同。
    • 一定要看看其他人发布的一些关于并发和摇摆的文章。
    • 在您的示例中,如果线程 #2 不是事件调度线程 (EDT),则更为可取。在 EDT 上调用 Thread.sleep(500) 将导致 UI 响应较慢。一个更好的选择是用 javax.swing.Timer 替换 Thread #2,它可以完成同样的事情,但不会阻塞 EDT。请注意,如果您使用 javax.swing.Timer(而不是 java.util.Timer),它将在 EDT 上调用其操作。
    【解决方案3】:

    由于您的应用程序与遗传算法有关,因此您可以在每一代左右更新 GUI。您可以通过在算法代码中实现 next() 方法并从 GUI 调用它来完成此操作。这应该很简单。

    但是,如果您真的不希望 GUI 在等待算法时冻结,您应该使用线程。

    【讨论】:

      【解决方案4】:

      抱歉 - 后台任务似乎是一件简单而明显的事情。不幸的是,Java Swing GUI 线程模型有点复杂。这方面已经有了一些改进,但仍然需要先了解一些线程知识。

      如果你有时间,我建议阅读Filthy Rich Clients 中关于线程的章节 - 通过 SwingWorker 进行无痛线程。

      如果您不耐烦,请阅读SwingWorker 上的 JavaDoc。如果您真的不耐烦,只需复制 JavaDoc 示例用法中的生命意义示例。

      【讨论】:

        【解决方案5】:

        Swing 的问题在于它是单线程的(这是一件好事),因此您希望将工作从 Swing 线程中移出,以便您的应用程序保持响应。

        您需要做的是将核心算法转换为 Runnable,因为它可以由 SwingWorker 和较新的 Executors 轻松处理(请参阅 Executors 了解许多预配置的)。您还可以创建调查如何将 PrintStream 制作为 JTextPanel,这样您就可以使用标准 println 语句来输出您当前的状态信息。

        如果你想添加一个停止按钮,你需要了解线程模型,这样你就知道如何控制它。 Java 教程在这方面以及一般的 Swing 编程方面都有很好的材料。强烈推荐。

        http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-19
          • 2014-02-23
          • 1970-01-01
          • 2017-01-25
          • 2012-07-01
          相关资源
          最近更新 更多