我一直在学习JavaFX的任务,并使用这些任务使用Platform.runLater
或任务的updateValue
方法等与Application线程进行通信。但是,我的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);
}
}
}
}
}
答案 0 :(得分:2)
我在这里要做的是考虑重构你正在做什么的方法,以避免两个不同线程之间的通信。例如,您可以将其视为一系列单独的任务,而不是将您正在做的事情视为一个长时间运行的任务,以便在UI完成时更新UI。 ScheduledService
class提供了管理这些任务的机制,并以清洁和安全的方式在它们和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
方法,而不是更改已在其他线程上运行的方法的行为。这可以避免任何低水平的&#34;关于同步的担忧。
例如:
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();
});
几乎总有一种方法可以重构代码以利用像这样的库类来避免线程之间的低级通信。