我一直在学习JavaFX的任务,并使用这些任务使用Platform.runLater
或任务的updateValue
方法等与应用程序线程通信。然而,我的Task
需要知道用户何时按下GUI上的按钮,因为这可能会更改任务的updateValue
方法所需返回的值。我该怎么做?我知道如何在单线程应用程序上响应按钮按下事件,但不确定如何以线程安全的方式处理它。
更新:
到目前为止,这就是我所拥有的,这是实现按钮事件的明智方式吗?
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.PixelFormat;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.control.Button;
import java.nio.IntBuffer;
public class TaskExample extends Application {
private Canvas canvas;
private PixelWriter pixel_writer;
@Override
public void start(Stage primaryStage) throws Exception {
canvas = new Canvas(256, 256);
pixel_writer = canvas.getGraphicsContext2D().getPixelWriter();
MyTask task = new MyTask();
task.valueProperty().addListener((c) -> {
if(task.getValue() != null) {
update(task.getValue());
}
});
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
Button button = new Button("Button 1");
// On the button click event it calls the eventFired() method
button.setOnAction((event) -> {
task.eventFired();
});
Pane pane = new VBox();
pane.getChildren().addAll(canvas, button);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public void update(IntBuffer data) {
pixel_writer.setPixels(
0,
0,
256,
256,
PixelFormat.getIntArgbInstance(),
data,
256
);
}
public static void main(String[] args) {
launch(args);
}
class MyTask extends Task<IntBuffer> {
public void eventFired() {
System.out.println("Event fired");
}
public void update(IntBuffer data) {
updateValue(data);
}
@Override
protected IntBuffer call() throws InterruptedException {
while(true) {
for (int i=0; i<3; i++) {
Thread.sleep(1000);
IntBuffer data = IntBuffer.allocate(256*256);
for(int j=0; j<256*256; j++) {
switch(i) {
case 0: data.put(0xFF0000FF); break;
case 1: data.put(0xFF00FF00); break;
case 2: data.put(0xFFFF0000); break;
}
}
data.rewind();
update(data);
}
}
}
}
}
我在这里要做的是思考如何重构您正在做的事情,以避免两个不同线程之间的通信。例如,不要把你正在做的事情看作是一个长期运行的任务,随着UI的进展而更新,而是把它看作是一系列单独的任务,每个任务完成后都会更新UI。ScheduledService
类提供了管理这些任务的机制,并以一种干净安全的方式在它们与FX应用程序线程之间进行通信:
import java.nio.IntBuffer;
import java.util.Arrays;
import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Button;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TaskExample extends Application {
private Canvas canvas;
private PixelWriter pixel_writer;
@Override
public void start(Stage primaryStage) throws Exception {
canvas = new Canvas(256, 256);
pixel_writer = canvas.getGraphicsContext2D().getPixelWriter();
MyService service = new MyService();
service.setPeriod(Duration.seconds(1));
service.valueProperty().addListener((ols, oldData, newData) -> {
if(newData != null) {
update(newData);
}
});
service.start();
Button button = new Button("Button 1");
Pane pane = new VBox();
pane.getChildren().addAll(canvas, button);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public void update(IntBuffer data) {
pixel_writer.setPixels(
0,
0,
256,
256,
PixelFormat.getIntArgbInstance(),
data,
256
);
}
public static void main(String[] args) {
launch(args);
}
class MyService extends ScheduledService<IntBuffer> {
// both instance variables accessed only on FX Application Thread:
private final int[] colors = {0xFF0000FF, 0xFF00FF00, 0xFFFF0000} ;
private int count = -1 ;
@Override
protected Task<IntBuffer> createTask() {
// invoked on FX Application Thread
count = (count + 1) % colors.length ;
return new MyTask(colors[count]);
}
}
class MyTask extends Task<IntBuffer> {
private final int color ;
MyTask(int color) {
// invoked on FX Application Thread:
this.color = color ;
}
@Override
protected IntBuffer call() {
// invoked on background thread:
IntBuffer data = IntBuffer.allocate(256*256);
int[] a = new int[256*256];
Arrays.fill(a, color);
data.put(a, 0, a.length);
data.rewind();
return data ;
}
}
}
您还没有非常具体地说明UI应该如何与后台线程交互,但如果您想在按下按钮时更改服务的行为,现在您需要更改在FX应用程序线程上调用的createTask
方法的行为,而不是更改已经在不同线程上运行的方法的行为。这避免了对同步的任何"低级"担忧。
例如:
class MyService extends ScheduledService<IntBuffer> {
// all instance variables accessed only on FX Application Thread:
private final int[][] colors = {
{0xFF0000FF, 0xFF00FF00, 0xFFFF0000},
{0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00}
};
private int count = -1 ;
private int scheme = 0 ;
@Override
protected Task<IntBuffer> createTask() {
// invoked on FX Application Thread
count = (count + 1) % colors[scheme].length ;
return new MyTask(colors[scheme][count]);
}
public void changeScheme() {
// invoked on FX Application Thread
scheme = (scheme + 1) % colors.length ;
}
}
然后只是
button.setOnAction(e -> service.changeScheme());
在此处添加对service.restart();
的调用将迫使更改尽快发生:
button.setOnAction(e -> {
service.changeScheme();
service.restart();
});
几乎总是有一种方法可以重构代码,以利用像这样的库类来避免线程之间的低级通信。