【问题标题】:Swing thread safety boilerplate摆动线程安全样板
【发布时间】:2011-09-07 03:38:01
【问题描述】:

为简单起见,想象一个下载​​文件的应用程序。有一个简单的 GUI,带有一个显示进度的标签。为了避免违反 EDT,像每个合法公民一样,我在一个线程(主)中下载文件,并在另一个线程(EDT)中更新 GUI。所以,这里是相关的伪代码块:

class Downloader {
    download() {
        progress.startDownload();
        while(hasMoreChunks()) {
            downloadChunk();
            progress.downloadedBytes(n);
        }
        progress.finishDownload();
    }
}

class ProgressDisplay extends JPanel {
    JLabel label;
    startDownload() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                label.setText("Download started");
            }
        });
    }
    downloadedBytes(int n) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                label.setText("Downloaded bytes: " + n);
            }
        });
    }
    finishDownload() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                label.setText("Download finished");
            }
        });
    }
}

我喜欢 Java 不支持闭包这一事实,而且代码对我来说非常清晰。除了笑话,我想知道......我做错了吗?是否有可能使用SwingUtilities 消除所有这些丑陋的样板,在每个方法中匿名实现Runnable 等等?

我的情况比这稍微复杂一些,但我试图通过实现代理或类似的东西来避免过度设计。

【问题讨论】:

  • 我不认为它是丑。与闭包相比,您基本上只需额外的new Runnable()
  • 和这个闭包比较? runInEdt({label.setText("Download started");}) 甚至:runInEdt(someBiggerAction),其中someBiggerAction 是方法名称。我知道我被污染了,这在 Java 中是不可能的,但我无法想象这么多年来每个人都违反 EDT 或编写这个 4 行包装器 100 次。
  • 正如我所说,唯一的区别是new Runnable。您现在可以拥有runInEdt。事实上你已经有了,它叫做SwingUtilities.invokeLater。 Java 有点罗嗦(按设计)。

标签: java swing boilerplate edt


【解决方案1】:

我会使用类似的辅助方法。

 public void setLabelText(final JLabel label, final String text) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            label.setText(text);
        }
    });
 }

【讨论】:

  • 对。这个例子显然过于简单了。如果您有 10 个面板,而不仅仅是标签怎么办?表格、进度条、按钮、动态删除和添加组件等等?这个简单案例的单个助手很好,但我不敢相信每个人都在为所有现实生活案例编写自己的 Swing 包装库。
  • @Konrad,我同意。 Swing 不遵循任何接口,因此您不能使用代理来为您执行此操作。面向方面的编程可能会有所帮助,但我自己倾向于避免使用它。
【解决方案2】:

该任务有一个特殊的类:SwingWorker。它允许您在单独的线程中运行代码并在工作结束时更新 UI。

【讨论】:

  • 谢谢,但请至少阅读问题。这是关于消除从非 Swing 线程更新 Swing 组件的样板,而不是“如何从 Swing 线程执行后台任务”。
  • 我同意@Vladimir。首先,Swing 组件不应该是线程安全的,因此在它们上定义调用 EDT 的方法看起来很奇怪。您应该假设始终从 EDT 调用 Swing 组件方法,就像所有 Swing 组件方法一样。你应该重新考虑你的整个程序设计,你可能最终会使用SwingWorker
  • 这不是重点。我最终会在某个地方拥有这个样板。 (我必须这样做吗?这就是我在这里要问的)
  • 在组件内执行此操作更有利于可测试性 - 您可以为 ProgressMonitor 定义接口并简单地调用 start()showProgress()stop() 而无需强制用户 (DownloadManager)记得关于 EDT。可以说,我会说这是一个更好的 API(我认为我的应用程序就是这种情况)。
  • 最后,你们完全没有抓住重点。我知道如何使用 Swing 中的工作程序以及如何与其他线程中的 Swing 交互。在我的情况下,它确实是后者——它不是一些从 UI 触发的任务的 GUI 应用程序。整个事情是一个复杂的算法,在主线程中运行,有时会向用户显示进度。
【解决方案3】:

几年前我们在一个项目中使用了Spin

【讨论】:

  • 类似于 Spin,是Foxtrot。两者似乎都被遗弃了。
【解决方案4】:

只是一个原始的想法:

public class ThreadSafeJLabel extends JLabel {

    @Override
    public void setText(final String text) {
        if (SwingUtilities.isEventDispatchThread()) {
            super.setText(text);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    setText(text);
                }
            });
        }
    }

}

没试过,但我猜当从 EDT 调用 setText() 时,将使用 super;但是当涉及到其他线程时,将在稍后调用被覆盖的 (ThreadSafeJLabel.setText()),这次是在 EDT 中。

【讨论】:

    【解决方案5】:

    在不引入大量冗余代码的情况下,您无法避免使用样板代码。但是你可以通过一个小的抽象帮助类和一些不寻常的格式让它变得更好一点。

    public abstract static class SwingTask implements Runnable
    {
        public void start()
        {
             SwingUtilities.invokeLater( this );
        }
    }
    
    startDownload() {
        new SwingTask() { public void run() {
            label.setText("Download started");
        } }.start();
    }
    

    代码格式旨在强调这一行中的所有代码基本上都是无趣的样板代码,可以轻松安全地复制到需要它的其他地方。我们已经使用这种格式样式有一段时间了,结果证明它非常有用。

    【讨论】:

      猜你喜欢
      • 2016-02-20
      • 2012-03-30
      • 2015-04-29
      • 2012-06-05
      • 1970-01-01
      • 2011-11-20
      • 1970-01-01
      • 2022-11-21
      • 1970-01-01
      相关资源
      最近更新 更多