Click here to Skip to main content
15,885,985 members
Articles / Programming Languages / Java

Java FX Foreground/Background Messaging

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
17 Jul 2019CPOL8 min read 11.5K   197   3  
This program demonstrates the methodology needed for by directional messaging between a Java FX foreground and 1 or more background threads.

Introduction

Java FX does not allow for the direct manipulation of screen data from a background thread because of memory addressing restrictions.

Only the Java FX foreground thread may make screen changes. Somehow, a background thread must inform the FX foreground of available data to process.

This program demonstrates:

  • how to implement a background's need to pass data to the FX foreground
  • technique for passing data from the FX foreground to a background thread
  • technique to avoid the FX foreground from blocking in the background
  • implementation of completely asynchronous foreground/background processing

This program uses the standard Java Scene Builder application to create the single user screen.

The program was developed using the MVC model for Java FX.

When using Java FX, each screen layout is stored in a separate fxml file.

The screen layout for this program is found in SimpleFXBG.fxml.

Java FX programs have a relatively small mainline class segment.

The mainline code class for this program is found in SimpleFXBG.java.

Screen initialization and event handlers use a controller class segment for each screen.

The controller for this program is SimpleFXBGController.java.

Class data for the controller is:

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

The @FXML tags are fields associated with the screen and whose properties are specified in the fxml file.

The event handler used to service the buttonSend control, is handleSendMessage, and is activated whenever the user wishes to send a message to the background.

The message that will be sent to the background is contained in the textTosend text property.

The event handler for buttonStop screen control is used to shutdown the program.

The FX ListView is used to display messages from the background on the FX screen control.

A FX ListView control does not directly get modified to add a new line to the control, rather an ObservableList is modified with each new background message.

At the screen control constructor instantiation, the ObservableList is bound to the ListView screen control.

There is not a lot of screen control property initialization because the properties have been previously defined using the Java FX Scene Builder and place in the fxml file.

The screen controller constructor is now simplified to:

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

The constructor creates a FXCollections.observableArrayList() for display messages from the background thread on the screen.

Next, the FXCollections.observableArrayList() is bound to the on screen ListView listMsgs.

Finally, an instantiation of the background class is created and the instantiation of the screen controller is sent to the background class.

The screen controller must be passed to the background so that the background knows where to send background message directed to the FX foreground.

It is worth noting that no queues with possible associated waiting are created in the FX foreground screen controller.

Sending a message from the FX foreground to the background is in the event handler for the Button event handler:

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

The only action in the event handler is to send the message to the background object created in the FX screen controller constructor.

This call should not block in the background.

When the background has a message directed at the FX foreground, the background invokes the onMessage of the FX controller.

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

The sole action of the onMessage event handler adds the background message to the observableArrayList.

Access to this method is why the FX controller instantiation is passed to the background constructor.

All action on the FX foreground controller has been defined now, the background must be examined.

SimpleBG.java

The background is composed of 4 classes:

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

SimpleBG.java

SimpleBG creates the shared resources used in the background.

It also instantiates the other three background classes, passing necessary shared resources to each class.

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

To handle queue of messages between the FX foreground and the background, two ArrayBlockingQueues are created, one for each direction.

outboundMsgQueue queues messages from the background destined to the FX foreground while inboundMsgQueue queues messages in the other direction.

These queues are the first hint of possible thread blocking.

The class constructor requires the FX class controller as discussed earlier in the FX class constructor.

Java
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(...)

The two queues are created.

This two objects have the potential for thread blocking.

Each queue is created with a specific maximum length. QUEUE_DEPTH

The queue true option is specified to guarantee FIFO ordering of messages.

SimpleBGConsumer.java is the background class for consuming messages from the FX foreground.

At creation, the consumer needs to know which shared resource queue to look at to consume message generated in the FX foreground.

SimpleBGSender.java is the background class for sending messages to the FX foreground.

This is background class the event interrupts the FX foreground is the sender and needs to know who the FX controller is and what queue to retrieve message destined for the FX foreground.

SimpleBGProducer.java is the background class that generates background messages destined for the FX foreground.

The producing needs to know the shared resource queue that holds messages it generates that are being directed to the FX foreground.

The other method in SimpleBG is:

Java
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(...)

This method is called by the FX foreground handleStopAction event handler.

The receiveMsg is passed in the message from the FX foreground destined to be consumed in the background.

It is important that when this method is called from the FX foreground that this method not block the FX foreground thread.

Trying to put a FX foreground message into an full ArrayBlockingQueue while causing the thread to block which is exactly what we do not want to happen to the invoking FX foreground thread.

To prevent blocking, somehow, the FX foreground thread must check if the queue is already full.

However, the check for queue full and then adding the message to the queue are separate lines of code and in a multi-threaded environment, the two lines need to be treated as atomic, i.e., not interruptable.

Placing the two lines within a synchronized block prevents on threads from interrupting the two lines.

Now that the lines are in synchronized block, the foreground thread need only wait a possible small time to get exclusive access to the queue.

The FX foreground tests to see if the queue is full.

When full, the FX foreground call is returned with a -1 to indicate the queue is full.

Returning when full prevents a blocking call putting a message in the queue.

If the queue is not full, the message from the FX foreground is placed in the queue of messages to be consumed.

While this method returns a failed (-1) or the number of items waiting in the queue to be consumed only a successful/non-successful is required.

The FX foreground could perform some action such as an error message popup message if failure is returned.

SimpleBGConsumer.java

The SimpleBGConsumer uses two local objects:

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

The other object is which queue holds messages generated by the FX foreground and directed to this background consumer thread.

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

The consumer background thread consumes any message placed in the consumer queue. The consumer background thread blocks if there is no message in the queue.

This simplistic consumer only displays the FX message.

However, it could be much more complex, e.g., it could send the message across the Internet. turn on or off a device like a light or pump, or perhaps log the message.

To satisfy the usage of the ArrayBlockingQueue, the InterruptedException needs to be caught.

Java
@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

The SimpleBGSender uses three local objects.

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

controller is the FX controller object. It was passed into the SimpleBG constructor from the FX controller and then passed into this class.

This provides the portal for informing the FX foreground when a background message is available.

simpleBGsenderThread is the background thread created by this class's constructor.

Again, yes, creating a thread in the constructor is poor coding practice.

The other object is which queue to place background generated messages that are directed to FX foreground.

The background thread of this class takes messages from the shared queue outBoundMsgQueue then sends them to a local method that does the interrupting of the FX foreground with the available background generated message.

This thread blocks waiting on an available message in outBoundMsgQueue.

Java
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()

The other local method does the interruption of the FX foreground to pass on the background generated message.

The argument being passed along to the FX foreground must be declared as final to satisfy FX foreground memory accessibility restrictions.

Java
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

The SimpleBGProducer uses three local objects.

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

simpleBGProducerThread is the background thread created by this class's constructor.

Again, yes, creating a thread in the constructor is poor coding practice.

The outboundMsgQueue object is which queue to place background generated messages that will be directed to FX foreground.

The final object FIVE_SECONDS is the wait time in milliseconds between background thread messages.

It is a good coding practice that is used to avoid embedding constants in code.

Similar to SimpleBGConsumer, this class saves the passed queue and starts a background thread.

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

The background thread is a simple loop that pauses this thread, then generates a new message and places the message in the queue of messages directed to the FX foreground.

Because the queue may block if full, the InterruptedException must be dealt with.

Java
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()

This concludes the Java FX foreground/background application.

It uses a variety of advanced features such as Java FX, multi-threading, and blocking arrays.

Four threads are used, the implicit FX foreground, plus 3 created background threads. consumer, producer, and sender.

This example passes around String messages but a more complex item could be passed.

Simplistic consumer and producer threads are created, but these can be modified in complexity to meet the needs of the application.

This approach is not limited to a single:

Java
public void onMessage(String argMsgToDisplay)

Additional event handlers for background generated message can be programmed using overloading of unique data types or using different unique names from onMessage.

An example of method overload could be:

Java
public void onMessage(String argMsgToDisplay)

and:

Java
public void onMessage(MyClassObject argMsgToDisplay)

An example of unique names could be:

Java
public void onMessageToList1(String argMsgToDisplay)

and:

Java
public void onMessageToList2(String argMsgToDisplay)

History

  • 2107/06/28: Initial submission

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --