从JavaFX中的不同线程更新UI

涡流

我正在开发带有几个TextField对象的应用程序,这些对象需要进行更新以反映关联的后端属性中的更改。TextFields为不可编辑,只有后端可能会改变他们的内容。

据我了解,正确的方法是在单独的线程上运行繁重的计算,以免阻塞UI。我使用javafx.concurrent.Task进行了此操作,并使用updateMessage()很好地将单个值传递回了JavaFX线程但是,我需要多个值来更新,因为后端需要处理。

由于后端值存储为JavaFX属性,因此我尝试将它们简单地绑定到textProperty每个GUI元素的,然后让绑定完成工作。但是,这不起作用。在运行了片刻之后,TextField即使后端任务仍在运行s也会停止更新。没有例外。

我还尝试使用Platform.runLater()来主动更新TextFields而不是绑定。这里的问题是,runLater()计划任务的速度比平台运行任务的速度快,因此GUI变得迟钝,即使后端任务完成后,GUI也需要时间来“赶上”。

我在这里发现了几个问题:

转换为UI的记录器条目停止随时间更新

JavaFX中的多线程会挂起UI

但我的问题仍然存在。

总结:我有一个对属性进行更改的后端,并且我希望这些更改出现在GUI上。后端是一种遗传算法,因此其操作可分为几代。我希望TextFields在两代之间至少刷新一次,即使这会延迟下一代。与GA快速运行相比,GUI的良好响应更为重要。

如果我还没有明确说明这个问题,我可以发布一些代码示例。

更新

我按照James_D的建议设法做到了。为了解决后端必须等待控制台打印的问题,我实现了一种缓冲的控制台。它将要打印的字符串存储在中,StringBufferTextAreaflush()调用方法将它们实际附加到我使用了AtomicBoolean来防止在刷新完成之前发生下一代操作,因为刷新是由Platform.runLater()可运行的操作完成的另请注意,此解决方案的速度非常慢。

詹姆斯_D

不知道我是否完全理解,但是我认为这可能会有所帮助。

Platform.runLater(...)为此使用一种合适的方法。

避免泛滥FX Application Thread的诀窍是使用Atomic变量来存储您感兴趣的值。在该Platform.runLater方法中,检索它并将其设置为哨兵值。在您的后台线程中,更新Atomic变量,但是只有Platform.runLater在将其重新设置为其前哨值的情况下,才发出新的变量

我通过查看的源代码了解了这一点Task看一下该updateMessage方法(撰写本文时的第1131行)是如何实现的。

这是使用相同技术的示例。这只是一个(繁忙的)后台线程,它尽可能快地计数,并更新IntegerProperty观察者监视该属性并AtomicInteger使用新值更新如果的当前值为AtomicInteger-1,则计划Platform.runLater

在中Platform.runLater,我检索的值AtomicInteger并使用它更新a Label,然后在过程中将其值重新设置为-1。这表明我已准备好进行另一个UI更新。

import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
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.layout.AnchorPane;
import javafx.stage.Stage;

public class ConcurrentModel extends Application {

  @Override
  public void start(Stage primaryStage) {
    
    final AtomicInteger count = new AtomicInteger(-1);
    
    final AnchorPane root = new AnchorPane();
    final Label label = new Label();
    final Model model = new Model();
    final NumberFormat formatter = NumberFormat.getIntegerInstance();
    formatter.setGroupingUsed(true);
    model.intProperty().addListener(new ChangeListener<Number>() {
      @Override
      public void changed(final ObservableValue<? extends Number> observable,
          final Number oldValue, final Number newValue) {
        if (count.getAndSet(newValue.intValue()) == -1) {
          Platform.runLater(new Runnable() {
            @Override
            public void run() {
              long value = count.getAndSet(-1);
              label.setText(formatter.format(value));
            }
          });          
        }

      }
    });
    final Button startButton = new Button("Start");
    startButton.setOnAction(new EventHandler<ActionEvent>() {
      @Override
      public void handle(ActionEvent event) {
        model.start();
      }
    });

    AnchorPane.setTopAnchor(label, 10.0);
    AnchorPane.setLeftAnchor(label, 10.0);
    AnchorPane.setBottomAnchor(startButton, 10.0);
    AnchorPane.setLeftAnchor(startButton, 10.0);
    root.getChildren().addAll(label, startButton);

    Scene scene = new Scene(root, 100, 100);
    primaryStage.setScene(scene);
    primaryStage.show();
  }

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

  public class Model extends Thread {
    private IntegerProperty intProperty;

    public Model() {
      intProperty = new SimpleIntegerProperty(this, "int", 0);
      setDaemon(true);
    }

    public int getInt() {
      return intProperty.get();
    }

    public IntegerProperty intProperty() {
      return intProperty;
    }

    @Override
    public void run() {
      while (true) {
        intProperty.set(intProperty.get() + 1);
      }
    }
  }
}

如果您确实想从UI“驱动”后端:即限制后端实现的速度,以便查看所有更新,请考虑使用AnimationTimer一个AnimationTimer具有handle(...)每帧一次渲染被调用。因此,您可以阻塞后端实现(例如,通过使用阻塞队列),并在每次调用handle方法时将其释放一次。handle(...)方法在FX Application Thread上调用。

handle(...)方法采用的参数是时间戳(以纳秒为单位),因此如果每帧一次过快,则可以使用该参数进一步减慢更新速度。

例如:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        
        final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);
        
        TextArea console = new TextArea();
        
        Button startButton = new Button("Start");
        startButton.setOnAction(event -> {
            MessageProducer producer = new MessageProducer(messageQueue);
            Thread t = new Thread(producer);
            t.setDaemon(true);
            t.start();
        });
        
        final LongProperty lastUpdate = new SimpleLongProperty();
        
        final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.
        
        AnimationTimer timer = new AnimationTimer() {

            @Override
            public void handle(long now) {
                if (now - lastUpdate.get() > minUpdateInterval) {
                    final String message = messageQueue.poll();
                    if (message != null) {
                        console.appendText("\n" + message);
                    }
                    lastUpdate.set(now);
                }
            }
            
        };
        
        timer.start();
        
        HBox controls = new HBox(5, startButton);
        controls.setPadding(new Insets(10));
        controls.setAlignment(Pos.CENTER);
        
        BorderPane root = new BorderPane(console, null, null, controls, null);
        Scene scene = new Scene(root,600,400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private static class MessageProducer implements Runnable {
        private final BlockingQueue<String> messageQueue ;
        
        public MessageProducer(BlockingQueue<String> messageQueue) {
            this.messageQueue = messageQueue ;
        }
        
        @Override
        public void run() {
            long messageCount = 0 ;
            try {
                while (true) {
                    final String message = "Message " + (++messageCount);
                    messageQueue.put(message);
                }
            } catch (InterruptedException exc) {
                System.out.println("Message producer interrupted: exiting.");
            }
        }
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

从不同类中的不同线程修改JavaFX gui

来自分类Dev

Eclipse ProgressMonitor在不同线程中访问UI

来自分类Dev

从不同线程更新AbstractTableModel

来自分类Dev

从不同线程同步请求JavaFX线程内的数据

来自分类Dev

QSqlDatabase不同线程中的并发查询

来自分类Dev

不同线程中SharedPreference的访问值

来自分类Dev

在不同线程中运行任务

来自分类Dev

QSqlDatabase不同线程中的并发查询

来自分类Dev

从不同线程中的QFile读取

来自分类Dev

从不同线程的Activity更新片段

来自分类Dev

访问不同线程中的不同列表元素

来自分类Dev

如何从Windows.UI.Xaml.Controls.TextBox中的不同线程中读取文本-Windows Phone 8.1

来自分类Dev

RabbitMQ中不同线程中的basicGet和basicAck

来自分类Dev

QTcpServer中的内存泄漏参与了不同线程中的连接

来自分类Dev

在C中的不同线程中从文件读取

来自分类Dev

从不同线程中的消息循环返回操作结果

来自分类Dev

在Android中的不同线程之间同步回调

来自分类Dev

Java列表从列表中的不同线程收集结果

来自分类Dev

如何从Rust中的不同线程写入文件?

来自分类Dev

在Perl中的不同线程中使用全局数组

来自分类Dev

是文件系统在javascript中的不同线程上运行

来自分类Dev

std::mutex 如何在不同线程中解锁?

来自分类Dev

在ndk中从不同线程调用java方法

来自分类Dev

在 Linux 中的不同线程之间缓冲 `printf` 输出

来自分类Dev

在不同线程中运行协程循环

来自分类Dev

在不同线程中的类之间传递变量?

来自分类Dev

如何在不同线程中处理 @KafkaListener 方法?

来自分类Dev

不同线程的输出分开

来自分类Dev

不同线程的输出分开

Related 相关文章

热门标签

归档