JavaFX: how to read CurrentTime of MediaPlayer while not in JavaFX App Thread [duplicate] - javafx

This question already has answers here:
Return result from javafx platform runlater
(2 answers)
Closed 3 years ago.
Is there a thread safe way to read properties of the JavaFX MediaPlayer (such as CurrentTime)?
To play/change a media I typically use Platform.runLater, but I need the CurrentTime immediately returned.
Is it safe to do myplayer.getCurrentTime()? Or will I get into trouble if the player is disposed off by another thread?
EXAMPLE:
private MediaPlayer player;
public void playMe(){
Platform.runLater(
new Runnable() {
#Override
public void run() {
player = new MediaPlayer(media);
player.play();
}});
}
public void deleteMe(){
Platform.runLater(
new Runnable() {
#Override
public void run() {
if (player != null) player.dispose();
player = null;
}});
}
public Double getCurrentTime(){
if (player != null) return player.getCurrentTime().toSeconds(); // thread issues???
else return null;
}

JavaFX properties are not thread-safe. Memory consistency is not guaranteed: Java is allowed to create a copy of the memory containing the property object for the background thread at any time. When this happens, any changes to the property happening later won't be visible to the background thread.
It's not too hard to make sure the value is accessible from the thread though. Depending on the frequency of the access and the delay you're willing accept for the information retrieval, the following approaches could work for you:
Updating a AtomicReference from a listener
This way you simply make sure the updates become visible to the background thread by assigning the value to a AtomicReference on the application thread:
final AtomicReference<Duration> time = new AtomicReference<>(player.getCurrentTime());
player.currentTimeProperty().addListener((o, oldValue, newValue) -> time.set(newValue));
Thread t = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
System.out.println(time.get());
}
});
t.setDaemon(true);
t.start();
The drawback is that the updates to the reference happen more often than necessary. (A volatile field may do the trick too btw.)
Querying the property using Platform.runLater
As alternative you could simply schedule a runnable reading the variable using Platform.runLater. This approach does not require a reference to be continuously updated:
Thread t = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
CompletableFuture<Duration> future = new CompletableFuture<>();
Platform.runLater(() -> future.complete(player.getCurrentTime()));
try {
Duration time = future.get(); // get value as soon as evaluated on application thread
System.out.println(time);
} catch (InterruptedException e) {
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
});
t.setDaemon(true);
t.start();
Note: for both approaches you need to deal with the fact that you may set the player field to null. Note that any test is subject to the same memory consistency issues as described at the start of the answer. The first approach would require you to make the field volatile to make sure the change is visible to the background thread too, for the second approach you could either cancel the future or throw cause an exception to notify the caller: future.completeExceptionally and future.cancel(true) result in future.get() yielding an ExecutionException or a CancelationException respecively.

Related

Why is ExecutorService not returning the results of my Task?

UPDATE:
I have a button on a JavaFx App that should do the login after user input email and password.
<Button fx:id="loginButton" layoutX="157.0" layoutY="254.0" mnemonicParsing="false" onAction="#login" prefHeight="30.0" prefWidth="172.0" text="Login" />
I have an ExecutorService on a JavaFX app. I created a task to search the DB. It executes the query well and gets the UserInfo object, the problem is, the ExecutorService is not passing the results to the main thread. This is the main code that is having a problem of null returned from the executor service:
public class LoginController {
#FXML
private Button loginButton;
#FXML
private Label afterLoginText;
#FXML
private TextField email;
#FXML
private PasswordField password;
#FXML
private Hyperlink hyperlink;
#FXML
private ProgressBar progressBar;
private Navegador navegador;
public void login(ActionEvent event) {
afterLoginText.setText("Login in, please wait...");
String emailText = email.getText();
String passwordText = password.getText();
DAOGeneric<UserInfo> dao = new DAOGeneric<>();
LoginAtDataBaseTask loginAtDataBaseTask = new LoginAtDataBaseTask(dao, emailText, passwordText);
progressBar.progressProperty().bind(loginAtDataBaseTask.progressProperty());
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(loginAtDataBaseTask);
loginAtDataBaseTask.setOnSucceeded(workerStateEvent -> {
UserInfo userInfo;
try {
userInfo = (UserInfo) future.get();
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (ExecutionException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
if(userInfo == null){
System.out.println("User info is null");
}
else{
try {
changeToMainScreen(event, userInfo);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
//Set premium level if user have one
//Optional - show premium info
}
});
executorService.shutdown();
}
public void changeToMainScreen(ActionEvent event, UserInfo userInfo) throws IOException {
Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
runMainScreen("/mainScreen.fxml",stage, userInfo);
}
And here is the code of the LoginAtDataBaseTask Class:
public class LoginAtDataBaseTask extends Task <UserInfo> {
private static DAOGeneric<UserInfo> dao;
private static String email;
private static String password;
public LoginAtDataBaseTask(DAOGeneric<UserInfo> dao, String email, String password) {
this.dao = dao;
this.email = email;
this.password = password;
}
#Override
protected UserInfo call() {
return doLogin();
}
private UserInfo doLogin(){
final int maxProgress = 100;
List<UserInfo> usersList = dao.findByAnyParameter("email", email, UserInfo.class);
if(usersList.size() == 1){
updateProgress(99,maxProgress);
UserInfo user1 = usersList.get(0);
String passwordDecoded = DecoderMD5.StringToMD5(password);
if(user1.getUserPassword().equals(passwordDecoded)){
// afterLoginText.setText("Login Sucess!");
return user1;
}
else{
// afterLoginText.setText("Wrong Password!");
}
}
else if(usersList.size()>1){
//More than one user with same email on BD
// afterLoginText.setText("Error code 1 - report to administrator");
}
else if(usersList.size()==0){
// afterLoginText.setText("This email is not registered! Please register first!");
}
else{
//Erro at DAO Search
// afterLoginText.setText("Error code 2 - report to administrator");
}
return null;
}
}
I tried casting on many ways and using Future first to receive the submit and then calling get on the future object, but nothing seems to work. I have already readed the java documents of this related classes but i don't really understand why my object keeps null.
UPDATE: I put a setOnSucceeded but the future.get keeps returning null, and the main javafx ui keeps freezing. What am i keeping doing wrong and what can i do to solve it?
Why does get() return null?
This has to do with the fact that Task is, fundamentally, an implementation of Runnable. It is not an implementation of Callable. Therefore, you are calling #submit(Runnable) which returns a Future<?>1, meaning no result is expected. A Runnable cannot return a value. In other words, the call to #get() will always return null in this case.
But you should really be calling #execute(Runnable) when passing a Task to an ExecutorService, anyway. There's no reason to have a Future object representing the status of the Task. This is for at least two reasons:
The call to Future#get() is a blocking call. The whole purpose of Task is to communincate a result specifically back to the JavaFX Application Thread. And you must never block that thread, as doing so will lead to an unresponsive UI.
A Task is a FutureTask2, which means it is already a Future. If you really need to wait for a result (not on the FX thread), then you can just call #get() on the Task instance.
1. It really should have been defined to return Future<Void>
2. Note that the standard ExecutorService implementation, ThreadPoolExecutor, wraps all submitted Runnable and Callable objects in a FutureTask, at least by default.
How to get the result of a Task
As noted earlier, the purpose of Task (and the other javafx.concurrent classes) is to offload work to a background thread but communicate a result (as well as messages, progress, etc.) back to the JavaFX Application Thread. That thread specifically, not any other thread. But you must not block the FX thread. That means observing the task for completion, not waiting for it to complete. Then when it does complete, you react by doing what needs doing.
But how to get the value from a completed Task? You query its value property, which will be set to whatever is returned by the #call() method if and when the task succeeds. You can directly observe this property with a listener, if you want. Personally, I prefer using the onSucceeded and onFailed properties. For example:
Task<SomeObject> task = ...;
task.setOnSucceeded(e -> {
SomeObject result = task.getValue();
// do something with 'result'
});
task.setOnFailed(e -> {
task.getException().printStackTrace(); // or log it with a proper logging framework
// notify user of failure
});
executor.execute(task);
Note properties of Task like message, progress, value and so on are guaranteed to only be set by the FX thread. And the onXXX handlers are also guaranteed to be invoked only by the FX thread.
See Concurrency in JavaFX and javafx.concurrent documentation for more information.

JavaFX many task work in GUI

I ran a thread that updates the open time of the application. It works well. I've expanded the Service class. The time from this task update my GUI, textField by Platform.runLater
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (!isCancelled()) {
if (isPause == false) {
try {
Platform.runLater(() -> {
currentTimeInApp = currentTimeInApp + 1;
upPanelController.timeInApp.setText
(currentTimeInApp.toString());
}
});
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
if (isCancelled())
break;
}
}
return null;
}
};
}
I would like to run a second thread which also updates GUI. I can't run the same thread. Can two independent threads be updated GUI ?
Most of the information on the internet is dedicated to one topic. Thank you for every suggestion
Yes, you can use as many threads as you like. You just have to make sure that you always do the GUI update via Platform.runLater.

JavaFX Task updateValue throws IllegalStateException: Not on FX application thread

I have a simple application with a single JavaFX window. I'm sending in data to an Azure IoTHub inside a for loop. This for loop is in a JavaFX Task, and the for loop has a small delay (Thread.sleep(300)) so progress can be shown on the UI. I have 2 labels I want to update during the data transmission, always showing the latest sent in data. I have the following helper class for this:
public class DataHelper {
private StringProperty date = new SimpleStringProperty();
private StringProperty count = new SimpleStringProperty();
public DataHelper() {
}
public DataHelper(String date, String count) {
this.date.setValue(date);
this.count.setValue(count);
}
//getters and setters
}
And here is my sendErrorsToHub method inside my UI controller class:
private void sendErrorsToHub(List<TruckErrorForCloud> toCloud) {
DataHelper dataHelper = new DataHelper("", "");
Task task = new Task<DataHelper>() {
#Override
public DataHelper call() {
try {
int i = 0;
for (TruckErrorForCloud error : toCloud) {
Thread.sleep(300);
i++;
String strMessage = Utility.toPrettyJson(null, error);
if (strMessage != null) {
Message msg = new Message(strMessage);
msg.setMessageId(java.util.UUID.randomUUID().toString());
client.sendEventAsync(msg, null, null);
}
updateProgress(i, toCloud.size());
DataHelper dh = new DataHelper(error.getErrorTimeStamp().substring(0, error.getErrorTimeStamp().length() - 9),
String.valueOf(error.getCount()));
updateValue(dh);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void updateValue(DataHelper value) {
super.updateValue(value);
dataHelper.setDate(value.getDate());
dataHelper.setCount(value.getCount());
}
//succeeded method omitted
};
dateValue.textProperty().bind(dataHelper.dateProperty());
countValue.textProperty().bind(dataHelper.countProperty());
progressBar.progressProperty().bind(task.progressProperty());
new Thread(task).start();
}
When I run the application, I constantly get IllegalStateException: Not on FX application threadexceptions, inside the updateValue method. As far as I understand the documentation, the whole point of the updateValue method, that it runs on the Application thread, and it can be used to pass a custom object, which can be used to update the UI.
What am I doing wrong then?
The bottom of the stacktrace with my classes is the following:
at eu.mantis.still_rca_simulator.gui.DataHelper.setDate(DataHelper.java:28)
at eu.mantis.still_rca_simulator.gui.GuiController$1.updateValue(GuiController.java:166)
at eu.mantis.still_rca_simulator.gui.GuiController$1.call(GuiController.java:155)
at eu.mantis.still_rca_simulator.gui.GuiController$1.call(GuiController.java:138)
(138 is the line Task task = new Task(), 155 updateValue(dh);, 166 dataHelper.setDate(value.getDate());)
updateValue does not automatically run on the application thread and it's not necessary to run it on the application thread since it takes care of updating the value property of Task on the application thread.
Your code in the overridden version updateValue executes logic on the background thread that needs to be run on the application thread though:
dataHelper.setDate(value.getDate());
dataHelper.setCount(value.getCount());
The bindings result in the text properties being updated from the background thread since the above code runs on the background thread.
In this case I recommend using a immutable DataHelper class and updating the ui using a listener to the value property:
Remove the updateValue override and the dataHelper local variable, initialize the gui with empty strings, if necessary, declare task as Task<DataHelper> task and do the following to update the gui:
task.valueProperty().addListener((o, oldValue, newValue) -> {
if (newValue != null) {
dateValue.setText(newValue.getDate());
countValue.setText(newValue.getCount());
}
});
You may also use Platform.runLater for those updates, since they don't happen frequently enough to result in issues that could be the result of using Platform.runLater too frequently.

JavaFX - Call "updateMessage" for TextArea from background Task - Two problems found

I am having two problems when trying to use "updateMessage" in a JavaFX task.
Issue #1
seems to be a known behavior, but I am not yet sure how exactly I can workaround it.This one is not (yet) critical to me.
The problem is that not all the updates I am performing in a background Task are displayed in the UI (at least the UI does not hang/freezes anymore, which was my initial issue).
My Code of the UI part:
TextArea console = new TextArea();
Button button01 = new Button("Start");
button01.setOnAction(new EventHandler() {
#Override
public void handle(Event event) {
if (ms.getState() == State.READY) {
ms.messageProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
console.appendText(newValue+"\n");
}
});
ms.start();
}
}
});
My Service:
public class MyService extends Service<Object> {
#Override
protected Task createTask() {
//here we use "MyTask" first to show problem #1
MyTask ct = new MyTask();
//here we use "MyTask2" first to show problem #2
// MyTask2 ct = new MyTask2();
try {
ct.call();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("MyService end");
return ct;
}
}
My Task (#1)
public class MyTask extends Task<Object> {
#Override
public EventHandler<WorkerStateEvent> call() {
System.out.println("call() is called");
if (Thread.currentThread().getName().equals("JavaFX Application Thread")){//yes, this might not be right, but if I do not do this, my stuff is executed twice because "call()" is called twice, but the textarea area is just updated in the second run (the non javafx application thread).
return null;
} else{
//actually here I want to do some 'heavy' stuff in the background
//and many things of this heavy stuff should be displayed / logged within the UI
//but very likely (hopefully) new messages (updateMessage) will not be send as fast as in the following loop
for (int i=0;i<10000000;i++){
updateMessage("This is update number'"+i+"' from the background thread");
}
Platform.runLater(new Runnable() {
#Override
public void run() {
try{
//here is the chance to get back to the view
}finally{
}
}
});
return null;
}
}
This basically works, but not every single loop is displayed in the UI.
How do I (correctly) make sure every loop is displayed?
Screenshot: Messages are displayed but not for every loop
Issue #2
Currently blocks my attempt to bring my little text-based game into a JavaFX application.
The main problem is that I am able to call "updateMessage" from the Task directly (see above), but not from a another (sub-)class which I would need to bring all message updates from my game (each message describes the progress of the game) to the UI.
The Task I use (Task #2):
public class MyTask2 extends Task<Object> {
#Override
public EventHandler<WorkerStateEvent> call() {
// ...
UITools myTools = new UITools();
myTools.logToUITest("Just one simple message");
// ...
Platform.runLater(new Runnable() {
#Override
public void run() {
try{
//here is the chance to get back to the view
}finally{
}
}
});
return null;
}
and the (sub-)class that I want to use to do the updateMessage (actually in my little game there would be even more classes that are called during the game and almost all of them trigger an update/message).
public class UITools {
public void logToUITest(String message){
updateMessage(message);
//how to allow 'updateMessage' from the Task to be executed from here?
}
This already results in "The method updateMessage(String) is undefined...".
How could I make it possible to call the updateMessage outside of the Task itself?
updateMessage() can only be called from within the call() method of a Task. It's a constraint imposed by the design of the Task class.
The missed message updates are due to the fact that there are too many updates and not all of them are forwarded to the event queue. Try to reduce the number of updates or sleep for a little while to separate them out in time

JavaFX auto-scroll auto-update text

Newbie question about JavaFX that I haven't been able to answer, despite knowing it must be pretty simple to do and not finding any resources on it anywhere I've looked (tutorials, many of the Oracle online docs, articles, the well-known JavaFX bloggers, etc.)
I'm developing a command line (script) running application and I have successfully gotten output (via ProcessBuilder) from the script that I can display in an ongoing manner, as things happen on the command line. That is, I can do System.out.println(line); all day long, showing the output in the console, which simply returns output from an input stream returned by the 'myProcess' that's running, created like this:
BufferedReader bri = new BufferedReader(new InputStreamReader(myProcess.getInputStream()))
So I am able to see all the output coming back from the script.
I'd like to set-up a JavaFX TextArea or ScrollPane or, not sure what, to display this output text (there's a lot of it, like several thousand lines) as an ongoing 'progress' of what's taking place in the script, as it happens. I have a Scene, I have buttons and get input from this scene to start the script running, but now I'd like to show the result of clicking the button "RUN THIS SCRIPT", so to speak.
I assume I need to create a TextArea as described here or perhaps a TextBuilder would be useful to begin making it. Not sure.
I need a bit of help in how to setup the binding or auto-scroll/auto-update part of this.
Can someone provide me a place to start, to do this with JavaFX? I'd rather not use Swing.
(I'm using JavaFX 2.2, JDK 1.7u7, all the latest stuff, and yes, this is an FXML app--so doing it that way would be preferred.)
UPDATE: Sergey Grinev's answer was very helpful in the binding part. But here is some more detail on what I mean when I ask for "a bit of help in how to setup" -- basically, I need to return control to the main Scene to allow the user to Cancel the script, or to otherwise monitor what's going on. So I'd like to "spawn" the process that runs that script (that is, have some kind of 'free running process'), but still get the output from it. (I wasn't very clear on that in my initial question.)
The technique I'm using here (see below) is to do a waitFor on the process, but of course this means the dialog/Scene is 'hung' while the script executes. I'd like to gain control back, but how do I pass the 'p' (Process) to some other controller piece (or alternatively, simply kick off that other process passing in the parameters to start the script and have it start the script) that will then do the auto-update, via the binding Sergey Grinev mentions--without 'hanging' the Scene/window? Also: Can I then 'stop' this other process if the user requests it?
Here is my current code ('waits' while script--which takes 20-40 min to run!--completes; this is not what I want, I'd like control returned to the user):
public class MyController implements Initializable {
#FXML
private void handleRunScript(ActionEvent event) throws IOException {
ProcessBuilder pb = new ProcessBuilder("myscript.sh", "arg1", "arg2", ...);
Process p = pb.start();
try {
BufferedReader bri = new BufferedReader
(new InputStreamReader(p.getInputStream()));
String line;
while ((line = bri.readLine()) != null) {
System.out.println(line);
textAreaRight.setText(line);
}
bri.close();
p.waitFor();
}
catch (Exception err) {
err.printStackTrace();
}
}
#FXML
private void handleCancel(ActionEvent event) {
doSomethingDifferent();
}
}
To log strings you can use TextArea
To make it asynchronious you need to make a separate thread for output reader.
public class DoTextAreaLog extends Application {
TextArea log = new TextArea();
Process p;
#Override
public void start(Stage stage) {
try {
ProcessBuilder pb = new ProcessBuilder("ping", "stackoverflow.com", "-n", "100");
p = pb.start();
// this thread will read from process without blocking an application
new Thread(new Runnable() {
#Override
public void run() {
try {
//try-with-resources from jdk7, change it back if you use older jdk
try (BufferedReader bri = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = bri.readLine()) != null) {
log(line);
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}).start();
stage.setScene(new Scene(new Group(log), 400, 300));
stage.show();
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public void stop() throws Exception {
super.stop();
// this called on fx app close, you may call it in user action handler
if (p!=null ) {
p.destroy();
}
}
private void log(final String st) {
// we can access fx objects only from fx thread
// so we need to wrap log access into Platform#runLater
Platform.runLater(new Runnable() {
#Override
public void run() {
log.setText(st + "\n" + log.getText());
}
});
}
public static void main(String[] args) {
launch();
}
}

Resources