进行多次更改触发会导致更少的操作

Make multiple change firings lead to far fewer actions

我有一个 TextArea,我的应用程序的用户可以在其中写东西。一个ChangeListener也在听这个TextAreaStringProperty"text"。每当文本内容更改时,ChangeListener.changed() 会在中央应用程序对象上将 "dirty" BooleanProperty 设置为 true。其中 "dirty" 具有 "document needs saving".

的含义

但我刚刚在我的应用程序中实现了一个功能,即任何时候 "dirty" Property 设置为 true 都会触发将文件保存到磁盘的操作,自动保存,因此用户不必担心手动保存内容。注意,保存的行为当然也会将 dirty 设置回 false

不过,这样做的一个问题是,它会减慢输入 TextArea 的速度(特别是因为这种保存是在 FX 线程上进行的)。因此,添加或删除每个新角色都会触发保存操作。

我想找到一个解决方案,其中每次更改文本操作之后总是最多在 1 秒内进行保存,但是每 1 秒保存不会超过一次 ...显然在非 FX 线程中。

这不是我第一次遇到这种情况,过去我曾摆弄过各种计时器队列等等。但是很难找到同时满足这两个标准的解决方案,我只是想知道是否有一种众所周知的技术来处理这个问题……甚至可能是某个图书馆的东西?

这是一个 MRE/MCVE:

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    BooleanProperty dirty;
    void setDirty(Boolean value) {
        dirtyProperty().set(value);
        if( value ){
            System.out.println( "serialise to file again...");
            // successful saving also means we become clean again:
            dirtyProperty().set( false );
        }
    }
    Boolean getDirty() { return dirtyProperty().get(); }
    BooleanProperty dirtyProperty() {
        if ( dirty == null) dirty = new SimpleBooleanProperty(this, "dirty");
        return dirty;
    }

    @Override
    public void start(Stage stage) throws Exception {
        Scene scene = new Scene(new Group());
        Group sceneRoot = (Group)scene.getRoot();
        TextArea textArea = new TextArea();

        textArea.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observableValue, String s, String t1) {
                setDirty( true );
            }
        });

        sceneRoot.getChildren().add( textArea );
        stage.setMinWidth( 600 );
        stage.setMinHeight( 400 );
        stage.setScene(scene);
        stage.show();
    }
}

每次击键都会保存一次...

不使用后台线程,而是使用暂停转换:

PauseTransition pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(e -> {
    // save action here
});

dirtyProperty().addListener((obs, wasDirty, isNowDirty) -> {
    if (isNowDirty && pause.getStatus() != Animation.Status.RUNNING) {
        pause.playFromStart();
    }
});

这将在 dirtyProperty() 变为 true 时开始一秒钟的暂停,并在暂停结束时保存数据。但是,通过检查暂停的状态,它不会安排超过每秒一次的保存。

如果你确实想使用后台线程,你可以按照以下几行做一些事情:

BlockingQueue<String> textToSave = new ArrayBlockingQueue<>(1);
ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
exec.scheduleWithFixedDelay(() -> {
    try {
        String text = textToSave.take();
        // save text
    } catch (InterruptedException interrupt) {
        Thread.currentThread().interrupt();
    }
}, 0, 1, TimeUnit.SECONDS);

然后

dirtyProperty().addListener((obs, wasDirty, isNowDirty) -> {
    textToSave.clear();
    textToSave.offer(myTextArea.getText());
});

这假定除 FX 应用程序线程外没有其他线程将数据推送到 textToSave "queue"(不确定大小 <= 1 的东西是否被正确称为 "queue") ,这似乎很容易确保。

这种方法的优点是 IO 发生在后台线程上,这意味着没有任何机会通过写入文件来阻止 FX 应用程序线程。