栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

在GUI线程中触发异步事件

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

在GUI线程中触发异步事件

听起来,您实际上真正需要的就是通过调用FX Application
Thread上的UI更新

Platform.runLater(...)
。这将安排一个更新,该更新将在FX Application
Thread有时间后立即执行,只要您没有过多的请求就可以很快地执行更新。下次出现渲染脉冲时,更新将对用户可见(因此从用户的角度来看,此更新将尽快发生)。

这是一个最愚蠢的示例:产生数据的异步类直接在UI上安排更新。

首先是一个简单的类来保存一些数据。我添加了一些功能来检查数据的“年龄”,即自构造函数被调用以来已经过了多长时间:

MyDataClass.java

public class MyDataClass {    private final int value ;    private final long generationTime ;    public MyDataClass(int value) {        this.value = value ;        this.generationTime = System.nanoTime() ;    }    public int getValue() {        return value ;    }    public long age() {        return System.nanoTime() - generationTime ;    }}

这是一个简单的UI,显示接收到的所有数据,以及数据的“年龄”和所有数据的平均值:

import javafx.beans.property.DoubleProperty;import javafx.beans.property.SimpleDoubleProperty;import javafx.scene.Parent;import javafx.scene.control.Label;import javafx.scene.control.textarea;import javafx.scene.layout.BorderPane;public class UI {    private final textarea textarea ;    private final Parent view ;    private long total ;    private long count ;    private final DoubleProperty average = new SimpleDoubleProperty(0);    public UI() {        textarea = new textarea();        Label aveLabel = new Label();        aveLabel.textProperty().bind(average.asString("Average: %.3f"));        view = new BorderPane(textarea, null, null, aveLabel, null);    }    public void registerData(MyDataClass data) {        textarea.appendText(String.format("data: %d (received %.3f milliseconds after generation)%n",      data.getValue(), data.age()/1_000_000.0));         count++;        total+=data.getValue();        average.set(1.0*total / count);    }    public Parent getView() {        return view ;    }}

这是一类(异步地)睡眠很多并产生随机数据的类(有点像我的实习生…)。目前,它仅具有对UI的引用,因此可以直接安排更新:

import java.util.Random;import javafx.application.Platform;public class DataProducer extends Thread {    private final UI ui ;    public DataProducer(UI ui) {        this.ui = ui ;        setDaemon(true);    }    @Override    public void run()  {        Random rng = new Random();        try { while (true) {     MyDataClass data = new MyDataClass(rng.nextInt(100));     Platform.runLater(() -> ui.registerData(data));     Thread.sleep(rng.nextInt(1000) + 250); }         } catch (InterruptedException e) { // Ignore and allow thread to exit        }    }}

最后是应用程序代码:

import javafx.application.Application;import javafx.scene.Scene;import javafx.stage.Stage;public class AsyncExample extends Application {    @Override    public void start(Stage primaryStage) {        UI ui = new UI();        DataProducer producer = new DataProducer(ui);        producer.start();        Scene scene = new Scene(ui.getView(), 600, 600);        primaryStage.setScene(scene);        primaryStage.show();    }    public static void main(String[] args) {        launch(args);    }}

运行此命令,我会在生成数据后约0.1毫秒内看到UI正在处理的数据,这符合您的要求。(第一个或第二个将花费更长的时间,因为它们是在start方法完成之前和物理显示UI之前生成的,因此对它们的调用

Platform.runLater(...)
将需要等待该工作完成。)


这段代码的问题当然是,它

DataProducer
与UI和JavaFX紧密耦合(
Platform
直接使用该类)。您可以通过授予一般消费者处理数据的方式来删除此耦合:

import java.util.Random;import java.util.function.Consumer;public class DataProducer extends Thread {    private final Consumer<MyDataClass> dataConsumer ;    public DataProducer(Consumer<MyDataClass> dataConsumer) {        this.dataConsumer = dataConsumer ;        setDaemon(true);    }    @Override    public void run()  {        Random rng = new Random();        try { while (true) {     MyDataClass data = new MyDataClass(rng.nextInt(100));     dataConsumer.accept(data);     Thread.sleep(rng.nextInt(1000) + 250); }         } catch (InterruptedException e) { // Ignore and allow thread to exit        }    }}

然后

import javafx.application.Application;import javafx.application.Platform;import javafx.scene.Scene;import javafx.stage.Stage;public class AsyncExample extends Application {    @Override    public void start(Stage primaryStage) {        UI ui = new UI();        DataProducer producer = new DataProducer(d -> Platform.runLater(() -> ui.registerData(d)));        producer.start();        Scene scene = new Scene(ui.getView(), 600, 600);        primaryStage.setScene(scene);        primaryStage.show();    }    public static void main(String[] args) {        launch(args);    }}

请注意,在

Consumer
此处设置a
与提供事件处理程序非常相似:每当生成数据元素时,都会“通知”或“触发”使用者。
List<Consumer<MyDataClass>>
如果您想通知多个不同的视图,可以轻松地将其扩展为一个,然后将使用者添加/删除到该列表中。数据类型
MyDataClass
扮演事件对象的角色:它包含有关发生的确切信息。
Consumer
是通用的功能接口,因此可以通过您选择的任何类或lambda表达式(如本例中所做的那样)来实现。

作为此版本的一个变体,您可以通过将a
抽象化为(这只是运行s的东西)来将的

Platform.runLater(...)
执行与执行分离开:
Consumer``Platform.runLater(...)``java.util.concurrent.Executor``Runnable

import java.util.Random;import java.util.concurrent.Executor;import java.util.function.Consumer;public class DataProducer extends Thread {    private final Consumer<MyDataClass> dataConsumer ;    private final Executor updateExecutor ;    public DataProducer(Consumer<MyDataClass> dataConsumer, Executor updateExecutor) {        this.dataConsumer = dataConsumer ;        this.updateExecutor = updateExecutor ;        setDaemon(true);    }    @Override    public void run()  {        Random rng = new Random();        try { while (true) {     MyDataClass data = new MyDataClass(rng.nextInt(100));     updateExecutor.execute(() -> dataConsumer.accept(data));     Thread.sleep(rng.nextInt(1000) + 250); }         } catch (InterruptedException e) { // Ignore and allow thread to exit        }    }}

import javafx.application.Application;import javafx.application.Platform;import javafx.scene.Scene;import javafx.stage.Stage;public class AsyncExample extends Application {    @Override    public void start(Stage primaryStage) {        UI ui = new UI();        DataProducer producer = new DataProducer(ui::registerData, Platform::runLater);        producer.start();        Scene scene = new Scene(ui.getView(), 600, 600);        primaryStage.setScene(scene);        primaryStage.show();    }    public static void main(String[] args) {        launch(args);    }}

解耦类的另一种方法是使用a

BlockingQueue
传输数据。它具有可以限制队列大小的功能,因此,如果有太多待处理数据,则数据生成线程将阻塞。此外,您可以在UI类中“批量处理”许多数据更新,这在您以足够快的速度生成它们以使FX
Application
Thread充满太多更新时非常有用(我在这里没有显示该代码;您需要使用a中的数据,
AnimationTimer
并进一步放松“立即”的概念。这个版本看起来像:

import java.util.Random;import java.util.concurrent.BlockingQueue;public class DataProducer extends Thread {    private final BlockingQueue<MyDataClass> queue ;    public DataProducer(BlockingQueue<MyDataClass> queue) {        this.queue = queue ;        setDaemon(true);    }    @Override    public void run()  {        Random rng = new Random();        try { while (true) {     MyDataClass data = new MyDataClass(rng.nextInt(100));     queue.put(data);     Thread.sleep(rng.nextInt(1000) + 250); }         } catch (InterruptedException e) { // Ignore and allow thread to exit        }    }}

UI需要做更多的工作:它需要一个线程来重复从队列中取出元素。请注意,

queue.take()
在没有可用元素之前,这些块将阻塞:

import java.util.concurrent.BlockingQueue;import javafx.application.Platform;import javafx.beans.property.DoubleProperty;import javafx.beans.property.SimpleDoubleProperty;import javafx.scene.Parent;import javafx.scene.control.Label;import javafx.scene.control.textarea;import javafx.scene.layout.BorderPane;public class UI {    private final textarea textarea ;    private final Parent view ;    private long total ;    private long count ;    private final DoubleProperty average = new SimpleDoubleProperty(0);    public UI(BlockingQueue<MyDataClass> queue) {        textarea = new textarea();        Label aveLabel = new Label();        aveLabel.textProperty().bind(average.asString("Average: %.3f"));        view = new BorderPane(textarea, null, null, aveLabel, null);        // thread to take items from the queue and process them:        Thread queueConsumer = new Thread(() -> { while (true) {     try {         MyDataClass data = queue.take();         Platform.runLater(() -> registerData(data));     } catch (InterruptedException exc) {         // ignore and let thread exit     } }        });        queueConsumer.setDaemon(true);        queueConsumer.start();    }    public void registerData(MyDataClass data) {        textarea.appendText(String.format("data: %d (received %.3f milliseconds after generation)%n",      data.getValue(), data.age()/1_000_000.0));         count++;        total+=data.getValue();        average.set(1.0*total / count);    }    public Parent getView() {        return view ;    }}

然后你就做

import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import javafx.application.Application;import javafx.scene.Scene;import javafx.stage.Stage;public class AsyncExample extends Application {    private final int MAX_QUEUE_SIZE = 10 ;    @Override    public void start(Stage primaryStage) {        BlockingQueue<MyDataClass> queue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE);        UI ui = new UI(queue);        DataProducer producer = new DataProducer(queue);        producer.start();        Scene scene = new Scene(ui.getView(), 600, 600);        primaryStage.setScene(scene);        primaryStage.show();    }    public static void main(String[] args) {        launch(args);    }}

同样,所有这些版本都可以通过

Platform.runLater(...)
计划更新来工作(存在各种将类解耦的机制)。至少从概念上讲,这实际上是将可运行对象放入一个无界队列中。FX
Application Thread从此队列中获取元素并运行它们(在该线程上)。因此,只要FX Application
Thread有机会,就立即执行runnable,这实际上是您可以实现的。

听起来好像不需要生成数据的线程才能阻塞数据,直到处理完数据为止,但是如果需要也可以实现(例如,只需将队列大小设置为1)。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/431357.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号