Posterior a de una larga búsqueda de datos resolvimos este atasco que pueden tener algunos los lectores. Te brindamos la solución y esperamos que te sea de mucha apoyo.
Solución:
Puede usar Timeline para el caso:
Timeline fiveSecondsWonder = new Timeline(
new KeyFrame(Duration.seconds(5),
new EventHandler()
@Override
public void handle(ActionEvent event)
System.out.println("this is called every 5 seconds on UI thread");
));
fiveSecondsWonder.setCycleCount(Timeline.INDEFINITE);
fiveSecondsWonder.play();
para los procesos en segundo plano (que no le hacen nada a la interfaz de usuario) puede usar el bien antiguo java.util.Timer
:
new Timer().schedule(
new TimerTask()
@Override
public void run()
System.out.println("ping");
, 0, 5000);
Prefacio: Esta pregunta suele ser el objetivo duplicado de las preguntas que preguntan cómo realizar acciones periódicas en JavaFX, si la acción debe realizarse en segundo plano o no. Si bien ya existen excelentes respuestas a esta pregunta, esta respuesta intenta consolidar toda la información proporcionada (y más) en una sola respuesta y explicar / mostrar las diferencias entre cada enfoque.
Esta respuesta se centra en las API disponibles en JavaSE y JavaFX y no en bibliotecas de terceros como ReactFX (que se muestra en la respuesta de Tomas Mikula).
Información de fondo: JavaFX y subprocesos
Como la mayoría de los marcos de GUI convencionales, JavaFX es de un solo subproceso. Esto significa que hay un solo hilo dedicado a leer y escribir el estado de la interfaz de usuario y procesar eventos generados por el usuario (por ejemplo, eventos del mouse, eventos clave, etc.). En JavaFX, este hilo se llama “hilo de aplicación JavaFX”, a veces abreviado a solo “hilo FX”, pero otros marcos pueden llamarlo de otra manera. Algunos otros nombres incluyen “subproceso de interfaz de usuario”, “subproceso de envío de eventos” y “subproceso principal”.
Es absolutamente primordial que todo lo que esté conectado a la GUI que se muestra en la pantalla solo se acceda o manipule en el Hilo de aplicación JavaFX. El marco JavaFX no es seguro para subprocesos y el uso de un subproceso diferente para leer o escribir incorrectamente el estado de la interfaz de usuario puede conducir a un comportamiento indefinido. Incluso si no ve ningún problema visible externamente, el acceso al estado compartido entre subprocesos sin la sincronización necesaria es un código roto.
Sin embargo, muchos objetos GUI pueden manipularse en cualquier hilo siempre que no estén “activos”. De la documentación de javafx.scene.Node
:
Los objetos de nodo se pueden construir y modificar en cualquier hilo siempre que aún no estén adjuntos a un
Scene
en unWindow
es decirshowing
[emphasis added]. Una aplicación debe adjuntar nodos a dicha escena o modificarlos en el subproceso de aplicación JavaFX.
Pero otros objetos GUI, como Window
e incluso algunas subclases de Node
(p.ej WebView
), son más estrictas. Por ejemplo, de la documentación de javafx.stage.Window
:
Los objetos de ventana deben construirse y modificarse en el subproceso de aplicación JavaFX.
Si no está seguro de las reglas de subprocesamiento de un objeto GUI, su documentación debe proporcionar la información necesaria.
Dado que JavaFX es de un solo subproceso, también debe asegurarse de nunca bloquear o monopolizar el subproceso FX. Si el hilo no tiene la libertad de hacer su trabajo, la interfaz de usuario nunca se vuelve a dibujar y los nuevos eventos generados por el usuario no se pueden procesar. No seguir esta regla puede llevar a la infame interfaz de usuario que no responde / congelada y sus usuarios no están contentos.
Prácticamente siempre está mal dormir el Hilo de aplicación JavaFX.
Tareas periódicas
Hay dos tipos diferentes de tareas periódicas, al menos para los propósitos de esta respuesta:
- Periódico primer plano “Tareas”.
- Esto podría incluir cosas como un nodo “parpadeante” o cambiar periódicamente entre imágenes.
- Periódico antecedentes Tareas.
- Un ejemplo podría ser la comprobación periódica de actualizaciones en un servidor remoto y, si las hay, descargar la nueva información y mostrársela al usuario.
Tareas periódicas en primer plano
Si su tarea periódica es corta y simple, entonces usar un hilo en segundo plano es excesivo y solo agrega una complejidad innecesaria. La solución más apropiada es utilizar el javafx.animation
API. Las animaciones son asincrónicas pero permanecen completamente dentro del Hilo de aplicación JavaFX. En otras palabras, las animaciones proporcionan una forma de “bucle” en el hilo FX, con retrasos entre cada iteración, sin utilizar realmente bucles.
Hay tres clases especialmente adecuadas para tareas periódicas en primer plano.
Cronología
A Timeline
se compone de uno o más KeyFrame
s. Cada KeyFrame
tiene un tiempo específico de cuándo debería completarse. Cada uno también puede tener un controlador “al finalizar” que se invoca después de que haya transcurrido la cantidad de tiempo especificada. Esto significa que puede crear un Timeline
con un solo KeyFrame
que ejecuta periódicamente una acción, repitiendo tantas veces como desee (incluso para siempre).
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application
@Override
public void start(Stage primaryStage)
Rectangle rect = new Rectangle(100, 100);
// toggle the visibility of 'rect' every 500ms
Timeline timeline =
new Timeline(new KeyFrame(Duration.millis(500), e -> rect.setVisible(!rect.isVisible())));
timeline.setCycleCount(Animation.INDEFINITE); // loop forever
timeline.play();
primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));
primaryStage.show();
Desde un Timeline
puede tener más de uno KeyFrame
es posible ejecutar acciones en diferentes intervalos. Solo tenga en cuenta que los tiempos de cada KeyFrame
no apilar. Si tienes uno KeyFrame
con un tiempo de dos segundos seguido de otro KeyFrame
con un tiempo de dos segundos, ambos KeyFrame
s finalizará dos segundos después de que se inicie la animación. Tener el segundo KeyFrame
terminar dos segundos después del primero, su tiempo debe ser cuatro segundos.
PauseTransition
A diferencia de las otras clases de animación, un PauseTransition
en realidad no está acostumbrado animar cualquier cosa. Su propósito principal es ser utilizado como hijo de SequentialTransition
para poner una pausa entre otras dos animaciones. Sin embargo, como todas las subclases de Animation
puede tener un controlador “al finalizar” que se ejecuta después de que se completa, lo que permite su uso para tareas periódicas.
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application
@Override
public void start(Stage primaryStage)
Rectangle rect = new Rectangle(100, 100);
// toggle the visibility of 'rect' every 500ms
PauseTransition pause = new PauseTransition(Duration.millis(500));
pause.setOnFinished(
e ->
rect.setVisible(!rect.isVisible());
pause.playFromStart(); // loop again
);
pause.play();
primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));
primaryStage.show();
Observe que el controlador finalizado invoca playFromStart()
. Esto es necesario para “repetir” la animación nuevamente. los cycleCount
La propiedad no se puede usar ya que el controlador finalizado no se invoca al final de cada ciclo, solo se invoca al final del último ciclo. Lo mismo es cierto de Timeline
; la razón por la que funciona Timeline
anterior se debe a que el controlador finalizado no está registrado con el Timeline
pero con el KeyFrame
.
Desde el cycleCount
la propiedad no se puede utilizar para PauseTransition
para múltiples ciclos, hace que sea más difícil realizar un bucle solo un cierto número de veces (en lugar de para siempre). Tienes que realizar un seguimiento del estado tú mismo y solo invocar playFromStart()
cuando sea apropiado. Manten eso en mente variables locales declarada fuera de una expresión lambda o clase anónima pero usada dentro de dicha expresión lambda o clase anónima debe ser final o efectivamente final.
Temporizador de animación
los AnimationTimer
class es el nivel más bajo de la API de animación de JavaFX. No es una subclase de Animation
y por lo tanto no tiene ninguna de las propiedades que se utilizaron anteriormente. En cambio, tiene un método abstracto que, cuando se inicia el temporizador, se invoca una vez por cuadro con la marca de tiempo (en nanosegundos) del cuadro actual: #handle(long)
. Para ejecutar algo periódicamente con AnimationTimer
(que no sea una vez por fotograma) requerirá calcular manualmente las diferencias de tiempo entre las invocaciones de handle
utilizando el argumento del método.
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class App extends Application
@Override
public void start(Stage primaryStage)
Rectangle rect = new Rectangle(100, 100);
// toggle the visibility of 'rect' every 500ms
AnimationTimer timer =
new AnimationTimer()
private long lastToggle;
@Override
public void handle(long now)
if (lastToggle == 0L)
lastToggle = now;
else
long diff = now - lastToggle;
if (diff >= 500_000_000L) // 500,000,000ns == 500ms
rect.setVisible(!rect.isVisible());
lastToggle = now;
;
timer.start();
primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));
primaryStage.show();
Para la mayoría de los casos de uso similares a los anteriores, use Timeline
o PauseTransition
sería la mejor opción.
Tareas periódicas en segundo plano
Si su tarea periódica requiere mucho tiempo (por ejemplo, cálculos costosos) o está bloqueada (por ejemplo, E / S), entonces se debe utilizar un subproceso en segundo plano. JavaFX viene con algunas utilidades de simultaneidad integradas para ayudar con la comunicación entre los subprocesos en segundo plano y el subproceso FX. Estas utilidades se describen en:
- El tutorial de Concurrencia en JavaFX, y
- La documentación de las clases en el
javafx.concurrent
paquete.
Para tareas periódicas en segundo plano que necesitan comunicarse con el hilo FX, la clase que se debe usar es javafx.concurrent.ScheduledService
. Esa clase ejecutará su tarea periódicamente, reiniciando después de una ejecución exitosa, en base a un período específico. Si está configurado para hacerlo, incluso reintentará una cantidad configurable de veces después de ejecuciones fallidas.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application
// maintain a strong reference to the service
private UpdateCheckService service;
@Override
public void start(Stage primaryStage)
service = new UpdateCheckService();
service.setPeriod(Duration.seconds(5));
Label resultLabel = new Label();
service.setOnRunning(e -> resultLabel.setText(null));
service.setOnSucceeded(
e ->
if (service.getValue())
resultLabel.setText("UPDATES AVAILABLE");
else
resultLabel.setText("UP-TO-DATE");
);
Label msgLabel = new Label();
msgLabel.textProperty().bind(service.messageProperty());
ProgressBar progBar = new ProgressBar();
progBar.setMaxWidth(Double.MAX_VALUE);
progBar.progressProperty().bind(service.progressProperty());
progBar.visibleProperty().bind(service.stateProperty().isEqualTo(State.RUNNING));
VBox box = new VBox(3, msgLabel, progBar);
box.setMaxHeight(Region.USE_PREF_SIZE);
box.setPadding(new Insets(3));
StackPane root = new StackPane(resultLabel, box);
StackPane.setAlignment(box, Pos.BOTTOM_LEFT);
primaryStage.setScene(new Scene(root, 400, 200));
primaryStage.show();
service.start();
private static class UpdateCheckService extends ScheduledService
@Override
protected Task createTask()
return new Task<>()
@Override
protected Boolean call() throws Exception
updateMessage("Checking for updates...");
for (int i = 0; i < 1000; i++)
updateProgress(i + 1, 1000);
Thread.sleep(1L); // fake time-consuming work
return Math.random() < 0.5; // 50-50 chance updates are "available"
;
Aquí hay una nota de la documentación de ScheduledService
:
El tiempo para esta clase no es absolutamente confiable. Un subproceso de eventos muy ocupado puede introducir un retraso de tiempo en el comienzo de la ejecución de la Tarea en segundo plano, por lo que es probable que valores muy pequeños para el período o retraso sean inexactos. Un retraso o período de cientos de milisegundos o más debería ser bastante confiable.
Y otro:
los
ScheduledService
introduce una nueva propiedad llamadalastValue
. loslastValue
es el valor que se calculó con éxito por última vez. Porque unService
limpia suvalue
propiedad en cada ejecución, y porque elScheduledService
reprogramará una ejecución inmediatamente después de su finalización (a menos que entre en el estado cancelado o fallido), elvalue
propiedad no es demasiado útil en unScheduledService
. En la mayoría de los casos, querrá utilizar en su lugar el valor devuelto porlastValue
.
La última nota significa atar a la value
propiedad de un ScheduledService
es con toda probabilidad inútil. El ejemplo anterior funciona a pesar de consultar el value
propiedad porque la propiedad se consulta en el onSucceeded
handler, antes de que se reprograme el servicio.
Sin interacción con la interfaz de usuario
Si la tarea periódica en segundo plano no necesita interactuar con la interfaz de usuario, puede utilizar las API estándar de Java. Más específicamente, ya sea:
- los
java.util.Timer
clase (nojavax.swing.Timer
), - O el mas moderno
java.util.concurrent.ScheduledExecutorService
interfaz.
Tenga en cuenta que ScheduledExecutorService
admite hilo quinielas, diferente a Timer
que solo admite un solo hilo.
ScheduledService no es una opción
Si por alguna razón no puedes usar ScheduledService
, pero necesita interactuar con la interfaz de usuario de todos modos, entonces debe asegurarse de que el código que interactúa con la interfaz de usuario, y solo ese código, se ejecute en el hilo FX. Esto se puede lograr usando Platform#runLater(Runnable)
.
Ejecute el Runnable especificado en el subproceso de aplicación JavaFX en algún momento no especificado en el futuro. Este método, que se puede llamar desde cualquier hilo, publicará el Runnable en una cola de eventos y luego regresará inmediatamente a la persona que llama. Los Runnables se ejecutan en el orden en que se publican. Un ejecutable pasado al método runLater se ejecutará antes de que cualquier Runnable pase a una llamada posterior a runLater. Si se llama a este método después de que se haya cerrado el tiempo de ejecución de JavaFX, la llamada se ignorará: no se ejecutará Runnable y no se lanzará ninguna excepción.
NOTA: las aplicaciones deben evitar inundar JavaFX con demasiados Runnables pendientes. De lo contrario, la aplicación puede dejar de responder. Aplicaciones se les anima a agrupar varias operaciones en menos llamadas runLater. Además, las operaciones de larga duración deben realizarse en un subproceso en segundo plano siempre que sea posible, liberando el subproceso de la aplicación JavaFX para las operaciones de la GUI.
[...]
Preste atención a la nota de la documentación anterior. los javafx.concurent.Task
La clase evita esto uniendo actualizaciones a su message
, progress
, y value
propiedades. Esto se implementa actualmente mediante el uso de un AtomicReference
y operaciones estratégicas de puesta en marcha. Si está interesado, puede echar un vistazo a la implementación (JavaFX es de código abierto).
Preferiría la transición de pausa:
PauseTransition wait = new PauseTransition(Duration.seconds(5));
wait.setOnFinished((e) ->
/*YOUR METHOD*/
wait.playFromStart();
);
wait.play();
Ten en cuenta difundir esta división si si solucionó tu problema.