进行多次更改触发会导致更少的操作
Make multiple change firings lead to far fewer actions
我有一个 TextArea
,我的应用程序的用户可以在其中写东西。一个ChangeListener
也在听这个TextArea
的StringProperty
"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 应用程序线程。
我有一个 TextArea
,我的应用程序的用户可以在其中写东西。一个ChangeListener
也在听这个TextArea
的StringProperty
"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 应用程序线程。