0

The question of the day is: how to call controllers function from another thread. My application looks like this:

public class Server {
//(...)
    public String setMsg(String string) {
       msg.set(string+"\n");
       mainScreenController.updateLog();
    }
//(...)
   while (true){
   doThings();
   }
}

    public class MainScreenController {
//(...)
    public void startServer(){
    new Thread(server::start).start();
    }
     public void updateLog(){
            Platform.runLater(()->{ testAreaLog.setText(testAreaLog.getText() + server.getMsg()); });
        }
//(...)
    }

I want to call updateLog() in the finally block, so every time server updates msg GUI adds this message to log window. My msg is

private volatile AtomicReference<String> msg = new AtomicReference<String>();

it works when i call updateLog(); in startServer(), it displays the first message Starting server as you may guessed, but calling another updateLog(); there returns null so I wanted to call it directly after getMsg() is used.

1337_
  • 23
  • 4
  • I can't understand what you're trying to do here. You want to call `updateLog()` from `getMsg()` and `getMsg()` from `updateLog()`? Won't that lead to infinite recursion (assuming one of them is called at some point from some code you haven't posted)? Why do you want the call to `updateLog()` in a `finally` block? And isn't your `Server` running on a single thread? Why does it need an `AtomicReference` for `msg`? And it makes absolutely no sense to make an `AtomicReference` `volatile`... – James_D May 23 '18 at 16:43
  • Sorry, it was supposted to be inside `setMsg()` of course. `Server` is called as another thread, which i want to synchronize with main javaFX thread to dinamically display new `msg`. `volatile` `AtomicReference` is a child of infertile trying to achieve synchronization. – 1337_ May 23 '18 at 17:10
  • Why don't you just let the `updateLog(...)` method have a string parameter, and simply pass `string` directly to it? Then you don't have to worry about any synchronization at all. (In fact, there's probably no reason to store the "latest message" in the `Server` instance.) – James_D May 23 '18 at 17:28
  • Well it cant work because I am updating log when something happen is endless loop in another thread so `mainScreenController.updateLog2("LOL");` just freezes the server – 1337_ May 23 '18 at 17:35
  • There's no way that can be caused by anything at all you have posted in your question. – James_D May 23 '18 at 17:35

1 Answers1

0

It's not really clear why you can't just do

public class MainScreenController {
    //(...)

    public void startServer(){
        new Thread(server::start).start();
    }

    public void updateLog(String message){
        Platform.runLater(()-> testAreaLog.appendText(message + "\n"));
    }

    //(...)
}

and

public class Server {

   public void start() {
       while (true){
           doThings();
           String newMessage = ... ;
           mainScreenController.updateLog(newMessage);
           doMoreThings();
       }
   }

}

This assumes something in your while loop is a blocking (or time-consuming) call, so that you are not flooding the UI thread with too many Platform.runLater() calls.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Ok, so where shall i initialize `MainScreenContoller` to call this method? I thinks its the problem now if you say that it cant be caused by anything I posted. ' public void start() { while (true) { MainScreenController mainScreenController = new MainScreenController(); mainScreenController.updateLog2("xD");' when i initialize it here server works, but `updateLog2(string)` throws `NullPointreException` in `JavaFX Thread`, if I initialize it at the beggining of `Server` it freezes even when its the first thing done by the loop. – 1337_ May 23 '18 at 17:50
  • @1337_ Yeah, you need to call `updateLog(...)` on the actual controller, not on some arbitrary object you create. None of the UI fields would be initialized in the new instance you create. (Actually, they won't be initialized in *any* of the multiple new instances you create on each iteration of your loop.) – James_D May 23 '18 at 17:51
  • Thats my problem, I dont know how to call in every time something in `Server` happens, for example its log for chat server, and someone connected, so in my loop I can just simply `System.out.println("Connection from: " + socket.getInetAddress());`, but how to input this text to GUI in proper time. – 1337_ May 23 '18 at 17:57
  • @1337_ You need to pass the server a reference to the actual controller. You can't just create new instances of the controller class and expect them somehow to magically know about the UI that was loaded from the FXML file. – James_D May 23 '18 at 17:58
  • ok, so shall I just pass to Server's constructon `Server(MainScreenController mainScreenController);` while creating the `Server` and everything should be fine? – 1337_ May 23 '18 at 18:11
  • @1337_ Probably... I have no idea where you are instantiating the `Server`, so... Did you try it? – James_D May 23 '18 at 18:12