multithreading - IO asincrónico en Scala con futuros
future (3)
Sí, me parece bien, pero es posible que desee investigar twitter-util o Akka Future más potentes (Scala 2.10 tendrá una nueva biblioteca de Future en este estilo).
Utiliza un grupo de hilos.
No, no lo hará. Debe usar el mecanismo estándar de su kit de herramientas GUI para esto (
SwingUtilities.invokeLater
for Swing oDisplay.asyncExec
for SWT). P.ejfimages.foreach (_.foreach(im => SwingUtilities.invokeLater(new Runnable { display im })))
Digamos que obtengo una lista (potencialmente grande) de imágenes para descargar desde algunas URL. Estoy usando Scala, entonces lo que haría es:
import scala.actors.Futures._
// Retrieve URLs from somewhere
val urls: List[String] = ...
// Download image (blocking operation)
val fimages: List[Future[...]] = urls.map (url => future { download url })
// Do something (display) when complete
fimages.foreach (_.foreach (display _))
Soy un poco nuevo para Scala, así que esto todavía se parece un poco a la magia para mí:
- ¿Esta es la manera correcta de hacerlo? Cualquier alternativa si no es?
- Si tengo 100 imágenes para descargar, ¿creará 100 hilos a la vez, o usará un grupo de hilos?
- ¿Se ejecutará la última instrucción (
display _
) en el hilo principal, y si no es así, cómo puedo asegurarme de que sea?
¡Gracias por su consejo!
Use futuros en Scala 2.10. Fueron un trabajo conjunto entre el equipo de Scala, el equipo de Akka y Twitter para alcanzar una API e implementación futuras más estandarizadas para su uso en todos los marcos. Acabamos de publicar una guía en: http://docs.scala-lang.org/overviews/core/futures.html
Más allá de ser completamente no bloqueante (de manera predeterminada, aunque ofrecemos la capacidad de realizar operaciones de bloqueo administradas) y composable, los futuros de Scala 2.10 vienen con un grupo de hilos implícitos para ejecutar sus tareas, así como algunas utilidades para administrar los tiempos de espera.
import scala.concurrent.{future, blocking, Future, Await, ExecutionContext.Implicits.global}
import scala.concurrent.duration._
// Retrieve URLs from somewhere
val urls: List[String] = ...
// Download image (blocking operation)
val imagesFuts: List[Future[...]] = urls.map {
url => future { blocking { download url } }
}
// Do something (display) when complete
val futImages: Future[List[...]] = Future.sequence(imagesFuts)
Await.result(futImages, 10 seconds).foreach(display)
Arriba, primero importamos una cantidad de cosas:
-
future
: API para crear un futuro. -
blocking
: API para bloqueo administrado. -
Future
: objeto compañero futuro que contiene varios métodos útiles para colecciones de futuros. -
Await
: objeto singleton utilizado para bloquear en un futuro (transfiriendo su resultado al hilo actual). -
ExecutionContext.Implicits.global
: el grupo de subprocesos global predeterminado, un grupo ForkJoin. -
duration._
: utilidades para gestionar las duraciones de tiempos muertos.
imagesFuts
sigue siendo básicamente el mismo que originalmente: la única diferencia aquí es que utilizamos el bloqueo de blocking
gestionado. Notifica al grupo de subprocesos que el bloque de código que le pasa contiene operaciones de larga ejecución o de bloqueo. Esto permite que el grupo genere temporalmente nuevos trabajadores para asegurarse de que nunca ocurra que todos los trabajadores estén bloqueados. Esto se hace para evitar la inanición (bloqueando el grupo de subprocesos) en aplicaciones de bloqueo. Tenga en cuenta que el grupo de subprocesos también sabe cuándo se completa el código en un bloque de bloqueo administrado, por lo que eliminará el subproceso de trabajador de repuesto en ese punto, lo que significa que el grupo se reducirá a su tamaño esperado.
(Si quiere evitar absolutamente que se creen hilos adicionales, entonces debe usar una biblioteca AsyncIO, como la biblioteca NIO de Java).
Luego usamos los métodos de recopilación del objeto acompañante Futuro para convertir imagesFuts
de List[Future[...]]
a Future[List[...]]
.
El objeto Await
es cómo podemos asegurarnos de que la display
se ejecuta en el hilo de llamada. Await.result
simplemente fuerza a que el hilo actual espere hasta que se complete el futuro en el que se pasó. (Esto usa bloqueo administrado internamente).
val all = Future.traverse(urls){ url =>
val f = future(download url) /*(downloadContext)*/
f.onComplete(display)(displayContext)
f
}
Await.result(all, ...)
- Use scala.concurrent.Future en 2.10, que ahora es RC.
- que usa un ExecutionContext implícito
- El nuevo documento de Future es explícito de que onComplete (y foreach) puede evaluar inmediatamente si el valor está disponible. Los viejos actores Future hacen lo mismo. Dependiendo de cuáles sean sus requisitos para la visualización, puede suministrar un ExecutionContext adecuado (por ejemplo, un ejecutor de hilos único). Si solo quiere que el hilo principal espere a que se complete la carga, poligonal le ofrece un futuro en el que esperar.