65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaFX 前台/后台消息传递

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2017年6月29日

CPOL

8分钟阅读

viewsIcon

12476

downloadIcon

229

该程序演示了JavaFX前台与一个或多个后台线程之间进行双向消息传递所需的方法。

引言

JavaFX 由于内存寻址限制,不允许后台线程直接操作屏幕数据。

只有JavaFX前台线程可以进行屏幕更改。后台线程必须设法通知FX前台有可用的数据需要处理。

该程序演示了

  • 如何实现后台需要将数据传递给FX前台
  • 将数据从FX前台传递到后台线程的技术
  • 避免FX前台在后台被阻塞的技术
  • 完全异步前台/后台处理的实现

该程序使用标准的Java Scene Builder应用程序创建单个用户屏幕。

该程序是使用JavaFX的MVC模型开发的。

在使用JavaFX时,每个屏幕布局都存储在单独的fxml文件中。

该程序的屏幕布局在SimpleFXBG.fxml中。

JavaFX程序的主线类部分相对较小。

该程序的主线代码类在SimpleFXBG.java中。

屏幕初始化和事件处理程序为每个屏幕使用一个控制器类部分。

该程序的控制器是SimpleFXBGController.java

控制器的类数据是

    @FXML Button buttonSend; 
    @FXML Button buttonStop; 
    @FXML ListView listMsgs; 
    @FXML TextField textToSend; 
    private ObservableList<String> msgData; 
    private SimpleBG simpleBG;

@FXML标签是与屏幕关联的字段,其属性在fxml文件中指定。

用于处理buttonSend控件的事件处理程序是handleSendMessage,当用户希望向后台发送消息时,该处理程序会被激活。

将要发送到后台的消息包含在textTosend文本属性中。

buttonStop屏幕控件的事件处理程序用于关闭程序。

FX的ListView用于在FX屏幕控件上显示来自后台的消息。

FX的ListView控件不会直接通过添加新行来修改,而是通过修改ObservableList来添加新的后台消息。

在屏幕控件构造函数实例化时,ObservableList会绑定到ListView屏幕控件。

屏幕控件的属性初始化不多,因为属性已通过JavaFX Scene Builder预先定义并放在fxml文件中。

屏幕控制器构造函数现在简化为

    msgData = FXCollections.observableArrayList(); 
    listMsgs.setItems(msgData); 
    simpleBG = new SimpleBG(this);

构造函数创建一个FXCollections.observableArrayList(),用于在屏幕上显示来自后台线程的消息。

接下来,FXCollections.observableArrayList()被绑定到屏幕上的ListView listMsgs

最后,创建后台类的实例,并将屏幕控制器的实例传递给后台类。

必须将屏幕控制器传递给后台,以便后台知道在哪里发送直接发往FX前台的后台消息。

值得注意的是,在FX前台屏幕控制器中没有创建任何队列,并且可能相关的等待。

从FX前台发送消息到后台是在Button事件处理程序的事件处理程序中完成的。

    @FXML 
    // Send message to background 
    private void handleSendAction(ActionEvent event) 
    { 
        int queueDepth = simpleBG.receiveMsg(textToSend.getText()); 
    }   // private void handleSendAction(...)

事件处理程序中的唯一操作是将消息发送到在FX屏幕控制器构造函数中创建的后台对象。

此调用不应在后台阻塞。

当后台有发往FX前台的消息时,后台调用FX控制器的onMessage

    //  Handle message from background 
    public void onMessage(String argMsgToDisplay) 
    { 
        msgData.add(argMsgToDisplay);     
    }   // pubic void onMessage(...)

onMessage事件处理程序的唯一操作是将后台消息添加到observableArrayList

访问此方法是为什么将FX控制器实例化传递给后台构造函数的原因。

FX前台控制器上的所有操作现已定义,现在必须检查后台。

SimpleBG.java

后台由4个类组成

  • SimpleBG.java
  • SimpleBGConsumer.java
  • SimpleBGProducer.java
  • SimpleBGSender.java

SimpleBG.java

SimpleBG创建后台使用的共享资源。

它还实例化了其他三个后台类,并将必要的共享资源传递给每个类。

    private final SimpleBGProducer producer; 
    private final SimpleBGConsumer consumer; 
    private final SimpleBGSender sender; 
    private final ArrayBlockingQueue<String> outboundMsgQueue; 
    private final ArrayBlockingQueue<String> inboundMsgQueue;

为了处理FX前台和后台之间的消息队列,创建了两个ArrayBlockingQueue,每个方向一个。

outboundMsgQueue队列化从后台发往FX前台的消息,而inboundMsgQueue队列化消息的另一个方向。

这些队列是潜在的线程阻塞的第一个线索。

类构造函数需要FX类控制器,如FX类构造函数前面所述。

    public SimpleBG(SimpleFXBGController argController) 
    { 
        // Create Q for consumer thread that may block 
        inboundMsgQueue = new ArrayBlockingQueue<>(QUEUE_DEPTH, true); 
        // Create Q for sender and producer 
        outboundMsgQueue = new ArrayBlockingQueue<>(QUEUE_DEPTH, true); 
        // Create consumer of messages from foreground 
        consumer = new SimpleBGConsumer(inboundMsgQueue); 
        // Create sender before producer 
        sender = new SimpleBGSender(argController, outboundMsgQueue); 
        // Create producer of message to foreground 
        producer = new SimpleBGProducer(outboundMsgQueue); 
    }   // public SimpleBG(...)

创建了两个队列。

这两个对象有潜在的线程阻塞。

每个队列都以特定的最大长度创建。QUEUE_DEPTH

队列的true选项被指定以保证消息的FIFO排序。

SimpleBGConsumer.java是用于从FX前台消耗消息的后台类。

创建时,消费者需要知道要查看哪个共享资源队列来消耗FX前台生成的消息。

SimpleBGSender.java是将消息发送到FX前台的后台类。

这是事件中断FX前台的后台类,发送方需要知道FX控制器是谁以及可以检索哪些队列中的消息来发往FX前台。

SimpleBGProducer.java是生成后台消息发往FX前台的后台类。

生产者需要知道持有它生成的、发往FX前台的消息的共享资源队列。

SimpleBG中的另一个方法是

    public int receiveMsg(String argInboundMsg) 
    { 
        // Restrict access to size & add are a joint atomic operation 
        synchronized(inboundMsgQueue) 
        { 
            int queueOccupancy; 
 
            // If Q full, don't block the FX foreground thread 
            if ((queueOccupancy = inboundMsgQueue.size()) == QUEUE_DEPTH) 
            { 
                return -1; 
            }   // if ((queueOccupancy = inboundMsgQueue.size()) == QUEUE_DEPTH) 
 
            // Add message from FX foreground (just above checked for space) 
            inboundMsgQueue.add(argInboundMsg); 
            return ++queueOccupancy; 
        }   // synchronized(argInboundMsg) 
    }   //  public int receiveMsg(...)

此方法由FX前台handleStopAction事件处理程序调用。

传入的receiveMsg是来自FX前台、将要被后台消耗的消息。

重要的是,当此方法从FX前台调用时,此方法不得阻塞FX前台线程。

尝试将FX前台消息放入已满的ArrayBlockingQueue会使线程阻塞,这正是我们不希望对调用FX前台线程发生的情况。

为了防止阻塞,后台线程必须以某种方式检查队列是否已满。

然而,检查队列是否已满和将消息添加到队列是分开的两行代码,在多线程环境中,这两行需要被视为原子操作,即不可中断。

将这两行放在同步块中可以防止其他线程中断这两行。

现在这两行在同步块中,前台线程只需要等待很短的时间就可以获得对队列的独占访问权。

FX前台测试队列是否已满。

当队列已满时,FX前台调用返回-1,表示队列已满。

返回时已满可防止阻塞调用将消息放入队列。

如果队列未满,则FX前台的消息将被放入消息队列以供消耗。

虽然此方法返回失败(-1)或待消耗的消息数量,但只需要成功/非成功。

FX前台可以执行一些操作,例如在返回失败时弹出错误消息。

SimpleBGConsumer.java

SimpleBGConsumer使用两个本地对象

    private final Thread simpleBGConsumerThread; 
    private final ArrayBlockingQueue<String> inboundMsgQueue;

另一个对象是持有FX前台生成并发往该后台消费者线程的消息的队列。

    public SimpleBGConsumer(ArrayBlockingQueue<String> argInboundMsgQueue) 
    { 
        inboundMsgQueue = argInboundMsgQueue; 
        // Thread can block waiting for data 
        simpleBGConsumerThread = new Thread(this); 
        simpleBGConsumerThread.start();         
    }   // public SimpleBGConsumer()

消费者后台线程消耗消费者队列中的任何消息。如果队列中没有消息,消费者后台线程将阻塞。

这个简单的消费者只显示FX消息。

然而,它可以更复杂,例如,它可以将消息通过互联网发送。打开或关闭像灯或泵这样的设备,或者记录消息。

为了满足ArrayBlockingQueue的使用,需要捕获InterruptedException

    @Override 
    public void run() 
    { 
        String inboundMsg; 
        String text; 
 
        while (true) 
        { 
            try 
            { 
                // Wait for message 
                inboundMsg = inboundMsgQueue.take(); 
                text = String.format("BG:'%s'", inboundMsg); 
                System.out.println(text); 
            }   // try 
            catch (InterruptedException ie) 
            { 
                break; 
            }   // catch (InterruptedException ie)             
        }   // while (true) 
    }   // public void run()

SimpleBGSender.java

SimpleBGSender使用三个本地对象。

    private final SimpleFXBGController controller; 
    private final Thread simpleBGSenderThread; 
    private final ArrayBlockingQueue<String> outBoundMsgQueue;

controller是FX控制器对象。它从FX控制器传递到SimpleBG构造函数,然后传递到这个类。

这提供了当后台消息可用时通知FX前台的门户。

simpleBGsenderThread是此类的构造函数创建的后台线程。

是的,在构造函数中创建线程是糟糕的编程实践。

另一个对象是放入发往FX前台的后台生成消息的队列。

此类后台线程从共享队列outBoundMsgQueue中获取消息,然后将它们发送到本地方法,该方法会中断FX前台并传递可用的后台生成的消息。

该线程阻塞等待outBoundMsgQueue中的可用消息。

    public void run() 
    { 
        String textToSend; 
 
        while(true) 
        { 
            try 
            { 
                // Wait for message to be sent to FX 
                textToSend = outBoundMsgQueue.take(); 
                sendMsgToFX(textToSend); 
            }   // try 
            catch (InterruptedException ie) 
            { 
                break; 
            }   // catch (InterruptedException ie) 
        }   // while(true) 
    }   // public void run()

另一个本地方法负责中断FX前台以传递后台生成的消息。

传递给FX前台的参数必须声明为final,以满足FX前台的内存可访问性限制。

    private void sendMsgToFX(final String argMsgToFX) 
    { 
        // Send msg to FX foreground thread 
        javafx.application.Platform.runLater 
        ( 
            () -> 
            { 
                controller.onMessage(argMsgToFX); 
            }   // () -> 
        );  // javafx.application.Platform.runLater 
    }   // private void sendMsgToFX(...)

SimpleBGProducer.java

SimpleBGProducer使用三个本地对象。

    private final ArrayBlockingQueue<String> outboundMsgQueue; 
    private final Thread simpleBGProducerThread; 
    private final int FIVE_SECONDS = 5000;

simpleBGProducerThread是此类的构造函数创建的后台线程。

是的,在构造函数中创建线程是糟糕的编程实践。

outboundMsgQueue对象是放入将发往FX前台的后台生成消息的队列。

最后一个对象FIVE_SECONDS是后台线程消息之间的等待时间(以毫秒为单位)。

这是避免在代码中嵌入常量的良好编码实践。

SimpleBGConsumer类似,此类保存传递的队列并启动后台线程。

    public SimpleBGProducer (ArrayBlockingQueue<String> argOutboundMsgQueue) 
    { 
        outboundMsgQueue = argOutboundMsgQueue; 
        // Thread can block of Q to FX is full 
        simpleBGProducerThread = new Thread(this); 
        simpleBGProducerThread.start();         
    }   // public SimpleBGProducer (...)

后台线程是一个简单的循环,它暂停该线程,然后生成一条新消息并将其放入发往FX前台的消息队列中。

由于队列可能会因为已满而阻塞,因此必须处理InterruptedException

    public void run() 
    { 
        String textToSend; 
        int msgSendNum = 0; 
 
        while(true) 
        { 
            try 
            { 
                // Generate new message every 5 seconds 
                Thread.sleep(FIVE_SECONDS); 
                textToSend = String.format("SM:Number %d", ++msgSendNum); 
                // Place in Q bound for FX foreground 
                outboundMsgQueue.add(textToSend); 
            }   // try 
            catch (InterruptedException ie) 
            { 
                break; 
            }   // catch (InterruptedException ie) 
        }   // while(true) 
    }   // public void run()

至此,JavaFX前台/后台应用程序结束。

它使用了各种高级功能,如JavaFX、多线程和阻塞数组。

使用了四个线程:隐式的FX前台,加上3个创建的后台线程:消费者、生产者和发送者。

本例传递的是String消息,但可以传递更复杂的项。

创建了简单的消费者和生产者线程,但可以根据应用程序的需求修改它们的复杂性。

此方法不限于单个

    public void onMessage(String argMsgToDisplay)

可以使用重载唯一数据类型或使用与onMessage不同的唯一名称来编程后台生成消息的附加事件处理程序。

方法重载的示例可以是

    public void onMessage(String argMsgToDisplay)

    public void onMessage(MyClassObject argMsgToDisplay)

唯一名称的示例可以是

    public void onMessageToList1(String argMsgToDisplay)

    public void onMessageToList2(String argMsgToDisplay)

历史

  • 2107/06/28:初始提交
© . All rights reserved.