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 实用程序类中的并发问题 的相关文章

  • 使用 java 从 XML 元素中删除空格

    我有一个 JSON 如下 String str Emp name JSON Emp id 1 Salary 20997 00 我想使用 java 将此 JSON 转换为 XML 我的 java 代码在这里 JSON json JSONSer
  • HibernateTemplate 可以与 EntityManager 共存吗?

    我们有一个 spring 3 应用程序 它仍然使用已弃用的HibernateTemplate为了持久性并希望迁移到更现代的 JPAEntityManager 是否可以在迁移过程中并行使用这两个 API 甚至可能在单个事务中同时使用 以便我们
  • 无法禁用 Firestore 中的离线数据

    从我的数据中删除数据后Firestore Database 这需要我的Android app一段时间后才意识到数据已被删除 我认为这是由于自动数据缓存而发生的 我的应用程序与离线使用无关 我想禁用此功能 我已将其添加到我的自定义中Appli
  • 使用 ScriptEngine 从 JavaScript 调用 Java 方法

    我正在使用 ScriptEngine 运行 JavaScript 我希望 JavaScript 脚本能够调用 myFunction 其中 myFunction 是我的给定类中的一个方法 我知道可以将 importPackage 用于标准 J
  • 在 libgdx 中批处理多维数据集时出现问题

    我正在尝试开发一款游戏 在屏幕上渲染多达 300 个立方体 为每个多维数据集创建新的 modelInstance 时 modelBatch 的性能非常糟糕 据我所知 没有 3d 批处理可以将所有立方体批处理到一次绘制调用 所以我拼命地尝试以
  • Google API - 重定向 URI 不匹配错误

    我正在我的网络应用程序上实现 google 登录 我通过参考this尝试过link https developers google com web signin server side flow 我的 google 登录按钮如下 div s
  • Spring MVC 和 Struts MVC 之间的区别 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 Spring MVC 和 Struts MVC 之间的主要区别是什么 Spring MVC 和 Struts 之间的主要区别是 Spr
  • 当Java中set已经是原子的时候,为什么我们还需要compareAndSet呢?

    因为原子意味着线程安全 当 set 本身在java中是原子和线程安全的时候 我们什么时候使用compareAndSet 举例来说 我想以原子方式设置一个变量 以便每个其他线程都可以看到它 但我希望以线程安全的方式设置该变量 我可以简单地将其
  • Java TCP Echo 服务器 - 广播

    我有一个简单的回显服务器 我希望当连接的用户向服务器键入任何内容时 所有其他客户端和该客户端都会收到消息 MOD 它现在不会发送给所有客户端 但它应该发送 而且我只是不知道我的代码出了什么问题 所以现在它只会将消息 MOD 发送给发送消息的
  • 使用 PowerMock 和 TestNG 模拟单个静态方法

    class StaticClass public static String a return a public static String ab return a b 我想嘲笑StaticClass a以便它返回 x 并致电StaticC
  • 在 XSSF 工作簿上设置密码保护

    我想为使用 poi 3 14 创建的 xlsx 文件添加密码保护 该文档声称 这是可能的 http poi apache org cryption html http poi apache org encryption html 使用我尝试
  • 异步不适用于控制器的抽象超类方法

    我有一个BaseRestControllerRest 控制器扩展的类 它有一个我想异步运行的方法 public abstract class BaseRestController Async someThreadPoolTaskExecut
  • CompletableFuture SupplyAsync

    我刚刚开始探索 Java 8 的一些并发特性 让我有点困惑的一件事是这两个静态方法 CompletableFuture
  • 错误:类 kotlin.reflect.jvm.internal.FunctionCaller$FieldSetter

    我已尝试一切方法来消除此错误 但它不断出现 Class kotlin reflect jvm internal FunctionCaller FieldSetter can not access a member of class com
  • 使用会话空闲超时进行轮询

    我对 Tomcat 中的所有应用程序使用单点登录 我的要求是 我必须轮询应从后端获取的事务状态 但它也不应该影响会话的空闲超时 有人可以建议是否可以做点什么吗 Thanx 我不知道是否有标准方法可以做到这一点 如果没有 你可以写一个过滤器
  • 将 JPanel 添加到 JFrame

    我有一个程序 其中将 JPanel 添加到 JFrame public class Test Test2 test new Test2 JFrame frame new JFrame Test frame setLayout new Bor
  • 在服务器上创建 Zip 文件并使用 java 下载该 zip

    我从 mkyong 获得了以下代码 用于在本地压缩文件 但是 我的要求是在服务器上压缩文件并需要下载它 任何人都可以帮忙吗 代码写入zip文件 public void zipFiles File contentFile File navFi
  • 如何在不下载子项的情况下从 Firebase 获取子项密钥?

    我有一个 Firebase 数据库 其中的节点 items 有很多子项 我想导入子项键的列表 由于每个子项都包含相当多我对此不感兴趣的数据 因此我想仅下载子项密钥 以最大程度地减少传输的数据量 为了便于说明 假设我有以下数据结构 然后我想获
  • 如何在 Android 应用程序退出之前进行一些清理?

    当我的 Android 应用程序终止时 是否有某种 onTerminate 方法可以进行一些清理 我想清除一些 SharedPreferences 我有一个活动 它保持几个数字的运行平均值 并将其存储在 SharedPreference 中
  • 如何读取FTL文件中的JSONArray?

    我在我的 Java 文件中硬编码了以下 JSON 对象 JSONObject notificationInfoJson new JSONObject notificationInfoJson put title Payment Receiv

随机推荐

  • 什么时候应该在 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 中可视化这些工作线程的进度 我所说的进度不仅指纯粹的百分比 还指任务类中未包含的其他变量 例如 例