JavaFX GUI Updater 实用程序类中的并发问题

2023-12-12

我正在 JavaFX 中为一个相当大的 Java 项目构建一个 GUI。该项目有许多不同的工作线程在后台进行一些繁重的计算,我试图在 GUI 中可视化这些工作线程的进度。我所说的进度不仅指纯粹的百分比,还指任务类中未包含的其他变量,例如(例如):

  • 当前文件
  • 当前错误计数
  • 到目前为止读取的字节数
  • ...

由于这些进度变量变化非常快,而且我必须从 JavaFX 线程 (Platform.runLater()) 进行 GUI 更新,因此 JavaFX 事件队列很快就会过载。我试图通过构建一个能够从 JavaFX 线程外部异步更新 GUI 属性的实用程序类来解决此问题。应跳过快速连续更新,以便仅显示最新值,从而避免 JavaFX 事件队列中充满 Runnable。

因此我建立了以下课程GUIUpdater将属性(通常是 GUI 元素,例如标签)绑定到 ObservableValues(例如 SimpleStringProperty)。这个类有两个内部类:

  • PropertyUpdater负责将单个 Property 绑定到单个 ObservableValue 并更新它。
  • The Updater为 Platform.runLater() 提供可重用的 Runnable 对象。

实用程序类:

package main;

import java.util.concurrent.ConcurrentLinkedQueue;

import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

/**
 * Class for enabling fast updates of GUI components from outside the JavaFX thread.
 *  Updating GUI components (such as labels) should be done from the JavaFX thread by using Platform.runLater for example.
 *  This makes it hard to update the GUI with a fast changing variable as it is very easy to fill up the JavaFX event queue faster than it can be emptied (i.e. faster than it can be drawn).
 *  This class binds ObservableValues to (GUI) Properties and ensures that quick consecutive updates are ignored, only updating to the latest value.
 */
public class GUIUpdater {
    private ConcurrentLinkedQueue<PropertyUpdater<?>>   dirtyPropertyUpdaters   =   new ConcurrentLinkedQueue<>();
    private Updater                                     updater                 =   new Updater();
    private boolean                                     isUpdating              =   false;

    /**
     * Binds an ObservableValue to a Property.
     *  Updates to the ObservableValue can be made from outside the JavaFX thread and the latest update will be reflected in the Property.
     * @param property      (GUI) Property to be updated/
     * @param observable    ObservableValue to update the GUI property to.
     */
    public <T> void bind(Property<T> property, ObservableValue<T> observable) {
        PropertyUpdater<T>  propertyUpdater = new PropertyUpdater<>(property, observable);
        observable.addListener(propertyUpdater);
    }

    /**
     * Unbinds the given ObservableValue from the given Property.
     *  Updates to the ObservableValue will no longer be reflected in the Property.
     * @param property      (GUI) Property to unbind the ObservableValue from.
     * @param observable    ObservableValue to unbind from the given Property.
     */
    public <T> void unbind(Property<T> property, ObservableValue<T> observable) {
        PropertyUpdater<T>  tmpPropertyUpdater = new PropertyUpdater<>(property, observable);
        observable.removeListener(tmpPropertyUpdater);
    }

    /**
     * Schedules an update to the GUI by using a call to Platform.runLater().
     *  The updated property is added to the dirtyProperties list, marking it for the next update round.
     *  Will only submit the event to the event queue if the event isn't in the event queue yet.
     * @param updater
     */
    private void scheduleUpdate(PropertyUpdater<?> updater) {
        this.dirtyPropertyUpdaters.add(updater);

        // Make sure the isUpdating var isn't changed concurrently by the Updater thread (on the JavaFX event queue)
        synchronized (this) {
            if (!this.isUpdating) {
                this.isUpdating = true;
                Platform.runLater(this.updater);
            }
        }
    }

    /**
     * Class used for binding a single ObservableValue to a Property and updating it.
     *
     * @param <T>
     */
    private class PropertyUpdater<T> implements ChangeListener<T> {
        private boolean             isDirty     =   false;
        private Property<T>         property    =   null;
        private ObservableValue<T>  observable  =   null;

        public PropertyUpdater(Property<T> property, ObservableValue<T> observable) {
            this.property = property;
            this.observable = observable;
        }

        @Override
        /**
         * Called whenever the ObservableValue has changed. Marks this Updater as dirty.
         */
        public synchronized void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
            if (!this.isDirty) {
                this.isDirty = true;
                GUIUpdater.this.scheduleUpdate(this);
            }
        }

        /**
         * Updates the Property to the ObservableValue and marks it as clean again.
         *  Should only be called from the JavaFX thread.
         */
        public synchronized void update() {
            T value = this.observable.getValue();
            this.property.setValue(value);
            this.isDirty = false;
        }

        @Override
        /**
         * Two PropertyUpdaters are equals if their Property and ObservableValue map to the same object (address).
         */
        public boolean equals(Object otherObj) {
            PropertyUpdater<?>  otherUpdater = (PropertyUpdater<?>) otherObj;
            if (otherObj == null) {
                return false;
            } else {
                // Only compare addresses (comparing with equals also compares contents):
                return (this.property == otherUpdater.property) && (this.observable == otherUpdater.observable);
            }
        }
    }

    /**
     * Simple class containing the Runnable for the call to Platform.runLater.
     *  Hence, the run() method should only be called from the JavaFX thread.
     *
     */
    private class Updater implements Runnable {

        @Override
        public void run() {
            // Loop through the individual PropertyUpdaters, updating them one by one:
            while(!GUIUpdater.this.dirtyPropertyUpdaters.isEmpty()) {
                PropertyUpdater<?>  curUpdater = GUIUpdater.this.dirtyPropertyUpdaters.poll();
                curUpdater.update();
            }

            // Make sure we're not clearing the mark when scheduleUpdate() is still setting it:
            synchronized (GUIUpdater.this) {
                GUIUpdater.this.isUpdating = false;
            }
        }

    }
}

这是一个用于测试的简单类GUIUpdater实用类:

package main;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class JavaFXTest extends Application {
    private GUIUpdater  guiUpdater  =   new GUIUpdater();
    private Label       lblState    =   new Label();
    private ProgressBar prgProgress =   new ProgressBar();

    public static void main(String args[]) {
        JavaFXTest.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // Init window:
        FlowPane    flowPane = new FlowPane();
        primaryStage.setScene(new Scene(flowPane));
        primaryStage.setTitle("JavaFXTest");

        // Add a Label and a progressBar:
        flowPane.getChildren().add(this.lblState);
        flowPane.getChildren().add(this.prgProgress);

        // Add button:
        Button  btnStart = new Button("Start");
        btnStart.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                // Create task:
                TestTask    testTask = new TestTask();

                // Bind:
                JavaFXTest.this.guiUpdater.bind(JavaFXTest.this.lblState.textProperty(), testTask.myStateProperty());
                JavaFXTest.this.prgProgress.progressProperty().bind(testTask.progressProperty());   // No need to use GUIUpdater here, Task class provides the same functionality for progress.

                // Start task:
                Thread  tmpThread = new Thread(testTask);
                tmpThread.start();
            }
        });
        flowPane.getChildren().add(btnStart);

        // Show:
        primaryStage.show();
    }

    /**
     * A simple task containing a for loop to simulate a fast running and fast updating process.
     * @author DePhille
     *
     */
    private class TestTask extends Task<Void> {
        private SimpleStringProperty    myState =   new SimpleStringProperty();

        @Override
        protected Void call() throws Exception {

            // Count:
            try {
                int maxValue = 1000000;

                System.out.println("Starting...");
                for(int i = 0; i < maxValue; i++) {
                    this.updateProgress(i, maxValue - 1);
                    this.myState.set("Hello " + i);
                }
                System.out.println("Done!");    
            } catch(Exception e) {
                e.printStackTrace();
            }

            // Unbind:
            JavaFXTest.this.guiUpdater.unbind(JavaFXTest.this.lblState.textProperty(), this.myStateProperty());
            return null;
        }

        public SimpleStringProperty myStateProperty() {
            return this.myState;
        }

    }

}

代码的问题在于,有时 Label 没有更新为最新值(在本例中为 999999)。这似乎主要发生在应用程序启动后,因此启动应用程序,单击“开始”按钮,关闭它并重复此过程应该会在几次尝试后重现问题。据我所知我已经添加了synchronized在需要的地方阻止,这就是为什么我不明白问题来自哪里。

尽管我主要是在寻找所描述问题的解决方案,但我们非常感谢所有建议(即使是那些与问题无关的建议)!我还在代码中添加了注释,因此我希望与上面的信息一起提供有关问题和代码的足够详细信息。

提前致谢!


我相信这个功能可以通过以下方式实现Task's messageProperty:

public void handle(ActionEvent event) {
    ...
    JavaFXTest.this.lblState.textProperty().bind(testTask.messageProperty());
    ...
}

...

protected Void call() throws Exception {
    ...
    this.updateProgress(i, maxValue - 1);
    this.updateMessage("Hello " + i);
    ...
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaFX GUI Updater 实用程序类中的并发问题 的相关文章

  • 如何让 BlazeDS 忽略属性?

    我有一个 java 类 它有一个带有 getter 和 setter 的字段 以及第二对 getter 和 setter 它们以另一种方式访问 该字段 public class NullAbleId private static final
  • 为什么 JTables 使 TableModel 在呈现时不可序列化?

    所以最近我正在开发一个工具 供我们配置某些应用程序 它不需要是什么真正令人敬畏的东西 只是一个具有一些 SQL 脚本生成功能并创建几个 XML 文件的基本工具 在此期间 我使用自己的 AbstractTableModel 实现创建了一系列
  • .properties 中的通配符

    是否存在任何方法 我可以将通配符添加到属性文件中 并且具有所有含义 例如a b c d lalalala 或为所有以结尾的内容设置一个正则表达式a b c anything 普通的 Java 属性文件无法处理这个问题 不 请记住 它实际上是
  • Spring AspectJ 在双代理接口时失败:无法生成类的 CGLIB 子类

    我正在使用Spring的
  • 过滤两次 Lambda Java

    我有一个清单如下 1 2 3 4 5 6 7 和 预期结果必须是 1 2 3 4 5 6 7 我知道怎么做才能到7点 我的结果 1 2 3 4 5 6 我也想知道如何输入 7 我添加了i gt i objList size 1到我的过滤器
  • 如何在 Spring 中禁用使用 @Component 注释创建 bean?

    我的项目中有一些用于重构逻辑的通用接口 它看起来大约是这样的 public interface RefactorAwareEntryPoint default boolean doRefactor if EventLogService wa
  • 如何更改javaFX中按钮的图像?

    我正在使用javaFX 我制作了一个按钮并为此设置了图像 代码是 Image playI new Image file c Users Farhad Desktop icons play2 jpg ImageView iv1 new Ima
  • 在接口中使用默认方法是否违反接口隔离原则?

    我正在学习 SOLID 原则 ISP 指出 客户端不应被迫依赖于他们所使用的接口 不使用 在接口中使用默认方法是否违反了这个原则 我见过类似的问题 但我在这里发布了一个示例 以便更清楚地了解我的示例是否违反了 ISP 假设我有这个例子 pu
  • 从最终实体获取根证书和中间证书

    作为密码学的菜鸟 我每天都会偶然发现一些简单的事情 今天只是那些日子之一 我想用 bouncy castle 库验证 java 中的 smime 消息 我想我几乎已经弄清楚了 但此时的问题是 PKIXparameters 对象的构建 假设我
  • 无法创建请求的服务[org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]-MySQL

    我是 Hibernate 的新手 我目前正在使用 Spring boot 框架并尝试通过 hibernate 创建数据库表 我知道以前也问过同样的问题 但我似乎无法根据我的环境找出如何修复错误 休眠配置文件
  • 在 junit 测试中获取 javax.lang.model.element.Element 类

    我想测试我的实用程序类 ElementUtils 但我不知道如何将类作为元素获取 在 AnnotationProcessors 中 我使用以下代码获取元素 Set
  • 帮助将图像从 Servlet 获取到 JSP 页面 [重复]

    这个问题在这里已经有答案了 我目前必须生成一个显示字符串文本的图像 我需要在 Servlet 上制作此图像 然后以某种方式将图像传递到 JSP 页面 以便它可以显示它 我试图避免保存图像 而是以某种方式将图像流式传输到 JSP 自从我开始寻
  • 如何对不同的参数类型使用相同的java方法?

    我的问题 我有 2 个已定义的记录 创建对象请求 更新对象请求 必须通过实用方法进行验证 由于这两个对象具有相同的字段 因此可以对这两种类型应用相同的验证方法 现在我只是使用两种方法进行重载 但它很冗长 public record Crea
  • 在我的 Spring Boot 示例中无法打开版本 3 中的 Swagger UI

    我在 Spring Boot 示例中打开 swagger ui 时遇到问题 当我访问 localhost 8080 swagger ui 或 localhost 8080 root api name swagger ui 时出现这种错误 S
  • 使用 AsyncTask 传递值

    我一直在努力解决这个问题 但我已经到了不知道该怎么办的地步 我想做的是使用一个类下载文件并将其解析为字符串 然后将该字符串发送到另一个类来解析 JSON 内容 所有部件都可以单独工作 并且我已经单独测试了所有部件 我只是不知道如何将值发送到
  • 最新的 Hibernate 和 Derby:无法建立 JDBC 连接

    我正在尝试创建一个使用 Hibernate 连接到 Derby 数据库的准系统项目 我正在使用 Hibernate 和 Derby 的最新版本 但我得到的是通用的Unable to make JDBC Connection error 这是
  • 我如何在java中读取二进制数据文件

    因此 我正在为学校做一个项目 我需要读取二进制数据文件并使用它来生成角色的统计数据 例如力量和智慧 它的设置是让前 8 位组成一个统计数据 我想知道执行此操作的实际语法是什么 是不是就像读文本文件一样 这样 File file new Fi
  • 如何使用mockito模拟构建器

    我有一个建造者 class Builder private String name private String address public Builder setName String name this name name retur
  • 使用 CXF-RS 组件时,为什么我们使用 而不是普通的

    作为后续这个问题 https stackoverflow com questions 20598199 对于如何正确使用CXF RS组件我还是有点困惑 我很困惑为什么我们需要
  • Spring Boot 无法更新 azure cosmos db(MongoDb) 上的分片集合

    我的数据库中存在一个集合 documentDev 其分片键为 dNumber 样本文件 id 12831221wadaee23 dNumber 115 processed false 如果我尝试使用以下命令通过任何查询工具更新此文档 db

随机推荐

  • 什么时候应该在 C++ 中使用 new 关键字?

    我使用 C 有一段时间了 我一直想知道new关键词 简单地说 我应该使用它还是不使用它 随着new关键词 MyClass myClass new MyClass myClass gt MyField Hello world 如果没有new关
  • Javascript - 离开页面时确认

    我正在尝试实现一个基本的弹出窗口 询问用户是否真的想要离开页面 类似于如果我尝试在编写此消息的过程中关闭窗口 则会在该网站上发生的情况 我意识到这通常会引起人们的不满 但我有充分的理由想要这样做 我通过使用以下代码使其工作 function
  • 子路径上有多个 Django 项目 + Nginx

    我正在尝试运行多个用 Django 编写的仪表板以在我的服务器上运行 但无法启动并运行它 已关注这个数字海洋教程并根据其进行修改这个答案 现在一切都已启动并正在运行 但是当我指向我的 URL 时 它显示 Nginx 欢迎页面http ipa
  • ?? Swift 中的运算符

    在 Swift 编程语言 一书中 第 599 页 中 我遇到了这段令我困惑的代码片段 事情是这样的 func buyFavoriteSnack person String throws let snackName favoriteSnack
  • IE 8 对每页样式表的数量有限制吗?

    In 关于 CSS 的回答 一位用户说道 据说 Internet Explorer has 有 4096 CSS 的限制rules每个文件 参考 此外 它对可以嵌入到单个文档中的样式表数量也有限制 我认为是20 虽然参考MSDN似乎证实了这
  • 列表中的平均分

    第一次发帖 如果写得不好请见谅 我在一个文件中有一份列表 其中包含学生的姓名 ID 分数等 见下文 我想计算另一个文件中的平均分数 但我不知道如何只取分数并将平均值写入另一个文件中 Thanks name surname student i
  • 我们可以在 Chrome 扩展程序中检索机器序列号吗?

    我们可以在 Chrome 扩展程序中检索机器序列号吗 例如 我可以通过在 shell 中执行以下命令来获取 Windows 中的序列号 wmic BIOS 获取序列号 如何在 Chrome 扩展程序中获取此序列号 不会 Chrome 扩展程
  • 使用 HTML 和 JavaScript 返回 PartialView

    我正在进行 AJAX 调用 使用 jQuery 来检索PartialView 除了 HTML 之外 我还想发回视图正在显示的对象的 JSON 表示形式 我一直使用的蹩脚方法是将属性作为隐藏输入嵌入到 HTML 中 这很快就会变得笨拙并且紧密
  • 实体数据栏和数据栏最小值的手动版本和编码版本之间的外观不一致

    我正在尝试在 EPPlus 4 0 4 中创建可靠的数据栏 但遇到了两个问题 首先 我一直无法弄清楚如何创建纯色填充颜色 其次 至少对于较小的值 条形图没有按照我期望的方式显示 下面的屏幕截图说明了这两个问题 在这两种情况下 所需的结果都是
  • VC++ 2008,OpenProcess 总是返回错误 5(访问被拒绝)

    有人知道为什么当我尝试使用 PROCESS ALL ACCESS 作为我所需的访问权限调用 OpenProcess 时 MSVC 2008 总是在 GetLastError 上返回错误 5 吗 PROCESS VM READ 工作得很好 我
  • 为什么我不能使用 pygame.image.load 作为类属性?它说“如果没有初始化 pygame.display 就无法转换”

    以下块产生错误cannot convert without pygame display initialized当用作类属性时 class Tile hidden image pygame image load image0 bmp hid
  • EF6 数据库首先将存储过程设为异步

    在异步模式下运行 EF6 存储过程 数据库优先 的正确方法是什么 我读到ToListAsync 但我没有看到存储过程可用 还不确定当实际调用返回 1 OUT 参数或 2 项目列表时是否有不同的方式来调用存储过程 Case 1 using D
  • 为什么必须分配一个指针才能使 realloc 工作而不改变内存块中的第一个值?

    int ptr realloc ptr count sizeof int or ptr realloc ptr count sizeof int 我注意到如果我多次使用选项号一 第一个内存地址的值 ptr指向 变为未定义 尽管内存块中的所有
  • Swift 3.0 删除字典数组中的重复项

    我正在努力删除 swift 3 0 中字典数组中的重复字典 下面是 let Dict1 String String messageTo Madhu let Dict2 String String messageTo Kiran let Di
  • python如何对int、str列表的列表进行排序[关闭]

    Closed 这个问题不符合堆栈溢出指南 目前不接受答案 给定一个 int str 列表 我需要找到一种方法将其从最高到最低排序 而不使用排序 所以如果我有 list 1 orange 3 banana 2 pear 1 apple 我应该
  • 如何从 SQL Server Management Studio 历史记录中删除“服务器名称”项目

    当尝试连接到 Management Studio 特别是 2008 中的服务器时 有一个字段可供您输入服务器名称 该字段还有一个下拉列表 其中显示您尝试连接的服务器的历史记录 如何删除单个项目 从那段历史 如何删除 登录字段历史记录中的项目
  • 确定 Cassandra 中分区的节点

    这可能是一个特殊的问题 但是是否可以确定分区键的节点 示例 我有一个分区键 id int 并且我使用默认值分区器 Murmur3Partitioner 具有 3 个节点和复制因子 1 我可以确定id 3的一个节点吗 CREATE TABLE
  • Java 中的字符串比较...? [复制]

    这个问题在这里已经有答案了 可能的重复 如何在 Java 中比较字符串 为什么第一次比较 s1 s2 显示相等 而第二次比较 s1 s3 显示不相等 public class StringComparison public static v
  • htaccess 反向目录

    是否可以让htaccess查找与url相关的特定文件 如果没有找到则返回上一步 Example example here where from Htaccess 会查看 example here where from 是否确实是某种类型的文
  • JavaFX GUI Updater 实用程序类中的并发问题

    我正在 JavaFX 中为一个相当大的 Java 项目构建一个 GUI 该项目有许多不同的工作线程在后台进行一些繁重的计算 我试图在 GUI 中可视化这些工作线程的进度 我所说的进度不仅指纯粹的百分比 还指任务类中未包含的其他变量 例如 例