当前位置: 首页 > 知识库问答 >
问题:

JavaFX应用程序线程到任务的通信

凌远
2023-03-14

我一直在学习JavaFX的任务,并使用这些任务通过Platform.runLater或任务的updateValue方法等与应用程序线程进行通信。但是,我的任务需要知道用户何时按下 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);
                }
            }
            
        }
        
    }
}

共有1个答案

景宏朗
2023-03-14

我在这里要做的是思考如何重构您正在做的事情,以避免两个不同线程之间的通信。例如,不要将您正在做的事情视为一个随着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();
});

几乎总有一种方法可以重构您的代码,以利用像这样的库类来避免线程之间的低级通信。

 类似资料:
  • 我正在构建一个多屏幕JavaFX应用程序,数据从SQL数据库拉到ObservableList,并通过Tableview显示在界面上。由于应用程序的多屏幕特性,我试图通过控制器将数据从ObservableList初始化到Tableview。SQL拉入ObservableList是通过新线程上的任务完成的。当我包含sqlCSEditTbl时。itemsProperty()。setValue((Obse

  • 我今天才开始学习JavaFX,我试图通过制作一个Snake克隆来了解更多,但是我在线程方面遇到了麻烦。我想创建一个线程来更新蛇在屏幕上的位置,但是不能以正常的Runnable线程方式使用它,因为我在线程中使用JavaFX来更新绘制到屏幕上的矩形的位置(我知道你不能这样做,必须使用任务,服务,Platform.run稍后,等等?)我创建线程的类扩展了JavaFX。scene.layout.窗格,我试

  • 我正在编写一个JavaFX应用程序,我的对象扩展任务提供了JavaFXGUI线程之外的并发性。 我的主要课程是这样的: 我的GUI控制器示例如下(略作抽象): 目前,我的任务只是进行睡眠并打印数字1到10: 我遇到的问题是,一旦任务完成,就好像启动任务的线程继续运行一样。因此,当我按下右上角的“X”退出JavaFX应用程序时,JVM继续运行,我的应用程序不会终止。如果你看一下我的主课,我已经把系统

  • 我有一个包含“资源管理器”类的多线程Java应用程序。 此类提供了一个资源列表,这些资源可以作为初始化参数请求。然后检查每个文件的本地文件系统,并将确定为本地的文件添加到列表中。 当类收到资源请求时,会发生以下情况之一: > 如果资源被确定为本地资源(在列表中):请提供可以找到它的URI。 如果资源是远程的(不在列表中):安排一个工作进程来获取资源。工作进程将在任务完成时通知经理,并更新本地资源列

  • 我有一个简单的控制台应用程序,有时需要执行图形操作,对于那些我使用JavaFx框架(有一些功能,我需要像css样式的文本)我只是生成一些形状和文本到一个隐藏的场景,然后保存在文件中仅此而已, 我知道要使用JavaFx,我必须将图形操作传递给JavaFx线程,但是当一切都完成后,我必须关闭应用程序(几个小时后),这个JavaFx线程仍然保持打开...我真的不想强行退出System.exit(),因为