multithreading - 为 JavaFX 任务实现暂停和恢复功能
问题描述
我正在为在后台运行的模拟器构建 UI。由于这个 Simulator 可能不会长时间保持,它当然必须在与 JavaFx Thread 不同的线程中。我想在单击相应按钮时启动、暂停、恢复和停止/终止模拟器。推进模拟器的服务类如下所示:
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class SimulatorService extends Service<Void> {
private Simulator simulator;
private long cycleLengthMS = 1000;
private final AtomicBoolean simulatorStopped = new AtomicBoolean(false);
public SimulatorService(Simulator simulator){
this.simulator = simulator;
}
@Override
protected Task<Void> createTask() {
return new Task<>() {
@Override
protected Void call() {
System.out.println("Requested start of Simulator");
int state;
do {
// advance
state = simulator.nextStep();
try {
TimeUnit.MILLISECONDS.sleep(cycleLengthMS);
if(simulatorStopped.get()){
super.cancel();
}
} catch (InterruptedException e) {
return null;
}
}
while (state == 0 && !simulatorStopped.get());
return null;
}
};
}
@Override
public void start(){
if(getState().equals(State.READY)){
simulatorStopped.set(false);
super.start();
}
else if(getState().equals(State.CANCELLED)){
simulatorStopped.set(false);
super.restart();
}
}
@Override
public boolean cancel(){
if(simulatorStopped.get()){
simulatorStopped.set(true);
return false;
} else{
simulatorStopped.set(true);
return true; //if value changed
}
}
}
如果按下 GUI 上的按钮,模拟器将启动服务:
import javafx.application.Platform;
import java.util.concurrent.atomic.AtomicInteger;
public class Simulator {
Model model;
private final SimulatorService simulatorService;
public Simulator(Model model){
this.model = model;
simulatorService = new SimulatorService(this);
}
public int nextStep(){
final AtomicInteger res = new AtomicInteger(0);
Platform.runLater(new Thread(()-> {
res.set(model.nextStep());
}));
return res.get();
}
public boolean stopSimulationService() throws IllegalStateException{
return simulatorService.cancel();
}
public void startSimulationService() throws IllegalStateException{
simulatorService.start();
}
}
如果模型中的观察值发生变化,则重新绘制部分窗口:
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class Model {
private final IntegerProperty observedValue = new SimpleIntegerProperty(0);
public int nextStep() {
observedValue.set(observedValue.get() + 1);
return observedValue.get() > 500000 ? 1 : 0;
}
public int getObservedValue() {
return observedValue.get();
}
public IntegerProperty observedValueProperty() {
return observedValue;
}
public void setObservedValue(int observedValue) {
this.observedValue.set(observedValue);
}
}
重绘发生在另一个类中:
import javafx.beans.value.ChangeListener;
public class ViewController {
private View view;
private Simulator simulator;
private Model model;
public ViewController(Simulator simulator) {
this.simulator = simulator;
this.view = new View();
setModel(simulator.model);
view.nextStep.setOnMouseClicked(click -> {
simulator.nextStep();
});
view.startSim.setOnMouseClicked(click -> {
simulator.startSimulationService();
});
view.stopSim.setOnMouseClicked(click ->{
simulator.stopSimulationService();
});
}
public View getView() {
return view;
}
private final ChangeListener<Number> observedValueListener = (observableValue, oldInt, newInt) -> {
view.updateView(newInt.intValue());
};
public void setModel(Model m) {
this.model = m;
m.observedValueProperty().addListener(observedValueListener);
}
}
对应视图:
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
public class View extends BorderPane {
Button nextStep = new Button("next step");
Button startSim = new Button("start");
Button stopSim = new Button("stop");
GridPane buttons = new GridPane();
Text num = new Text();
public View(){
buttons.add(nextStep,0,0);
buttons.add(startSim,0,1);
buttons.add(stopSim,0,2);
buttons.setAlignment(Pos.BOTTOM_LEFT);
setCenter(buttons);
setTop(num);
}
public void updateView(int num){
this.num.setText("" + num);
}
}
主要的:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
ViewController c = new ViewController(new Simulator(new Model()));
Scene scene = new Scene(c.getView(),200,200);
stage.setScene(scene);
stage.show();
}
}