hilos - JavaFX 2: fondo y Platform.runLater vs Task/Service
task javafx (1)
Las clases de Task
y Service
están diseñadas para fomentar las buenas prácticas y el uso adecuado de la concurrencia para algunos (pero no todos) los escenarios comunes en la programación de GUI.
Un escenario típico es que la aplicación necesita ejecutar alguna lógica en respuesta a una acción del usuario que puede llevar mucho tiempo (tal vez un cálculo largo o, más comúnmente, una búsqueda en la base de datos). El proceso devolverá un resultado que luego se utilizará para actualizar la interfaz de usuario. Como ya sabe, el proceso de ejecución prolongada debe ejecutarse en un subproceso de fondo para mantener la UI receptiva, y la actualización de la interfaz de usuario se debe ejecutar en el subproceso de la aplicación FX.
La clase Task
proporciona una abstracción para este tipo de funcionalidad y representa una tarea "única" que se ejecuta y produce un resultado. El método call()
se ejecutará en el subproceso en segundo plano, y está diseñado para devolver el resultado del proceso, y hay detectores de eventos para cuando finaliza la tarea que se notifican en el subproceso de la aplicación FX. Se recomienda encarecidamente al desarrollador inicializar la implementación de Task
con estado inmutable y hacer que el método call()
devuelva un objeto inmutable, lo que garantiza una sincronización adecuada entre el hilo de fondo y el hilo de la aplicación FX.
Existen requisitos comunes adicionales en este tipo de tareas, como actualizar un mensaje o el progreso a medida que avanza la tarea. La aplicación también puede necesitar monitorear el estado del ciclo de vida de la clase (esperando para ejecutarse, en ejecución, completado, fallido con una excepción, etc.). Programar esto correctamente es bastante sutilmente difícil, ya que implica necesariamente acceder al estado mutable en dos subprocesos diferentes, y hay muchos desarrolladores de aplicaciones que desconocen las sutilezas. La clase Task
proporciona ganchos simples para este tipo de funcionalidad y se encarga de toda la sincronización.
Para usar esta funcionalidad, simplemente cree una Task
cuyo método call()
devuelve el resultado de su cálculo, registre un controlador para cuando el estado SUCCEEDED
de RUNNING
a SUCCEEDED
, y ejecute la tarea en una SUCCEEDED
fondo:
final Task<MyDataType> task = new Task<MyDataType>() {
@Override
public MyDataType call() throws Exception {
// do work here...
return result ;
}
};
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent event) {
MyDataType result = task.getValue(); // result of computation
// update UI with result
}
});
Thread t = new Thread(task);
t.setDaemon(true); // thread will not prevent application shutdown
t.start();
La forma en que esto funciona entre bastidores es que la Task
mantiene una propiedad de state
, que se implementa utilizando una ObjectProperty
JavaFX regular. La Task
sí misma está envuelta en una implementación privada de Callable
, y la implementación Callable
es el objeto que se pasa al constructor de la superclase. En consecuencia, el Callable
call()
Callable
es en realidad el método ejecutado en el hilo de fondo. El Callable
call()
Callable
se implementa de la siguiente manera:
- Programe una llamada en el hilo de la aplicación FX (es decir, utilizando
Platform.runLater()
) que actualice elstate
, primero aSCHEDULED
, luego aRUNNING
- Invoque el método
call()
de laTask
(es decir, el métodocall()
desarrollado por el usuario) - Programe una llamada en el subproceso de la aplicación FX que actualice la propiedad del
value
al resultado del métodocall()
- Programe una llamada en el subproceso de la aplicación FX que actualice la propiedad del
state
aSUCCEEDED
Este último paso, por supuesto, invocará a los oyentes registrados con la propiedad del state
, y dado que el cambio de estado se invocó en el subproceso de la aplicación FX, también lo harán los métodos handle()
dichos oyentes.
Para una comprensión completa de cómo funciona esto, vea el código fuente .
Comúnmente, la aplicación puede querer ejecutar estas tareas varias veces, y monitorear el estado actual que representa todos los procesos (es decir, "ejecutar" ahora significa que se está ejecutando una instancia, etc.). La clase de Service
simplemente proporciona un contenedor para esto a través de un método createTask()
. Cuando se inicia el Service
, obtiene una instancia de la Task
llamando a createTask()
, la ejecuta a través de su Executor
y transiciona su propio estado en consecuencia.
Por supuesto, hay muchos casos de uso de simultaneidad que no encajan (al menos de forma limpia) en las implementaciones de Task
o Service
. Si tiene un Thread
fondo único que se ejecuta durante toda la duración de la aplicación (por lo que representa un proceso continuo, en lugar de una tarea única), la clase Task
no encaja bien. Ejemplos de esto pueden incluir un bucle de juego o (quizás) sondeo. En estos casos, es mejor que utilices tu propio Thread
con Platform.runLater()
para actualizar la interfaz de usuario, pero, por supuesto, debes manejar la sincronización adecuada de las variables a las que pueden acceder ambos subprocesos. En mi experiencia, vale la pena pasar un tiempo pensando si estos requisitos se pueden reorganizar en algo que encaje en el modelo de Task
o Service
, ya que si esto se puede hacer, la estructura del código resultante suele ser mucho más limpia y fácil de administrar. . Sin embargo, hay casos en los que este no es el caso, en cuyo caso es apropiado usar un Thread
and Platform.runLater()
.
Un último comentario sobre sondeo (o cualquier otro requisito para una tarea de fondo programada periódicamente). La clase de Service
parece un buen candidato para esto, pero resulta bastante difícil administrar la periodicidad de manera efectiva. JavaFX 8 introdujo una clase de servicio ScheduledService
que se encarga de esta funcionalidad bastante bien, y también agrega manejo para casos como fallas repetidas de la tarea en segundo plano.
Estoy bastante confundido sobre el concepto de Task
/ Service
en JavaFX.
He usado un modelo basado en un hilo de fondo para mi trabajo de fondo, que llama a Platform.runLater
para cualquier actualización de la UI.
Digamos que no estoy interesado en una barra de progreso o tal. Estoy haciendo un trabajo real en mi modelo que debe actualizarse a la vista de la GUI (por ejemplo, una lista de participantes que se actualiza con el tiempo en función de alguna conexión en segundo plano, lista de participantes basada en la entrada de algún usuario, clasificada por edad) y origen). Esto es lo que suelo lograr con los hilos de fondo que empiezo, y dentro de los cuales utilizo Platform.runLater
.
Ahora en JavaFX 2 tienen toda esta simultaneidad usando las Task
y los Service
, sugiriendo que es mejor usarlos. Pero no veo ningún ejemplo que logre lo que estoy diciendo.
Actualizar la barra de progreso vinculando algunas propiedades es bueno (pero esas son información sobre la tarea, no su modelo).
Entonces, ¿cómo puedo actualizar el contenido de mis vistas según mi modelo? ¿Debería llamar a Platform.runLater
desde dentro de la Task
? Si no, ¿cuál es el mecanismo? ¿Cómo capto cuándo las tareas han tenido éxito y obtengo el resultado (la actualización del modelo real) para actualizar la vista?
Desafortunadamente, los tutoriales de Oracle no fueron muy buenos en este sentido. Señalarme algunos buenos tutoriales también ayudaría.