【问题标题】:JavaFX Application Thread to Task CommunicationJavaFX 应用程序线程到任务通信
【发布时间】:2016-07-29 09:11:38
【问题描述】:

我一直在学习 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);
                }
            }
            
        }
        
    }
}

【问题讨论】:

  • 您发布的代码没有问题,但实际上并没有按照您的描述进行:即两个线程不交互。当您按下按钮时,任务将在 FX 应用程序线程上向控制台显示一条消息。但是没有任何改变会影响后台线程上发生的事情。因此,如果您想问“这行得通吗”,您需要实际展示一些符合您描述的代码。
  • 啊,我没有意识到他们没有互动。如何在后台线程上运行eventFired 方法?
  • 你不能在后台线程上运行它。但是大概只是向控制台发送消息并不是您真正想要做的。你想做一些改变call 方法中发生的事情的事情(除了你在这个阶段没有人知道),不是吗?
  • 我想要实现的是让call 方法知道按钮已被按下。因此,根据您对@Eudy Contreras 的回答的评论,您会建议原子变量和侦听器是要走的路吗?
  • 我认为以线程安全的方式更改变量的状态是您想要做的。原子引用是使该线程安全的一种简单方法(但可能有点矫枉过正,具体取决于细节)。 JavaFX 属性是单线程的,因此在此处使用这些属性和侦听器将非常困难(充其量)。

标签: java multithreading user-interface javafx concurrency


【解决方案1】:

我在这里要做的是考虑如何重构你正在做的事情以避免两个不同线程之间的通信。例如,不要将您正在做的事情视为一个长期运行的任务,它会随着 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 应该如何与后台线程交互,但是如果您想在按下按钮时更改服务的行为,您现在将更改 @ 的行为987654324@ 方法,在 FX 应用程序线程上调用,而不是更改已经在不同线程上运行的方法的行为。这避免了有关同步的任何“低级”问题。

例如:

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();
});

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

【讨论】:

    猜你喜欢
    • 2016-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-30
    相关资源
    最近更新 更多