A Future[T] represents a T-typed result, which may or may not be delivered at some time in the future, or which may fail altogether. Like real-world asynchronous operations, a future is always in one of 3 states: incomplete, completed successfully, or completed with a failure."
A Future[T] represents a T-typed result, which may or may not be delivered at some time in the future, or which may fail altogether. Like real-world asynchronous operations, a future is always in one of 3 states: incomplete, completed successfully, or completed with a failure."
Los futures sirven para componer acciones asíncronas y paralelas de forma fácil y razonable.
A Future[T] represents a T-typed result, which may or may not be delivered at some time in the future, or which may fail altogether. Like real-world asynchronous operations, a future is always in one of 3 states: incomplete, completed successfully, or completed with a failure."
Los futures sirven para componer acciones asíncronas y paralelas de forma fácil y razonable.
Son ideales para cómputos asíncronos que no requieren estado. Por ejemplo consultas externas. También pueden servir para ejecutar side effects asíncronamente.
Friedman y Wise introdujeron el término promesa en 1976 (The Impact of Applicative Programming on Multiprocessing)
Baker y Hewit (el mismo del modelo de actores) propusieron los futuros implícitos para hacer evaluación paralela en lenguajes funcionales. [The incremental garbage collection of processes (1977)]
Friedman y Wise introdujeron el término promesa en 1976 (The Impact of Applicative Programming on Multiprocessing)
Baker y Hewit (el mismo del modelo de actores) propusieron los futuros implícitos para hacer evaluación paralela en lenguajes funcionales. [The incremental garbage collection of processes (1977)]
Las primeras nociones de futuros implicaban que para usar el valor dentro del futuro este debía forzarse (bloquearse)
Friedman y Wise introdujeron el término promesa en 1976 (The Impact of Applicative Programming on Multiprocessing)
Baker y Hewit (el mismo del modelo de actores) propusieron los futuros implícitos para hacer evaluación paralela en lenguajes funcionales. [The incremental garbage collection of processes (1977)]
Las primeras versiones no hablaban de cómo crear nuevos futuros a partir de otros
Las primeras nociones de futuros implicaban que para usar el valor dentro del futuro este debía forzarse (bloquearse)
Aplicaciones en sistemas distribuidos para hacer RPC
Aplicaciones en sistemas distribuidos para hacer RPC
Finagle, la librería de Twitter para hacer RPC, esta cimentada en la composición de Futuros
Future { miCodigo() }
Para ejecutar un bloque de código dentro de un Future basta con hacer:
Future { miCodigo() }
Para ejecutar un bloque de código dentro de un Future basta con hacer:
implicit val ec: ExecutionContext = ...
def ejecutarFuturo(): Future[Int] = Future {
Thread.sleep(2000)
println("Future completed!")
4+5
}
ejecutarFuturo()
println("Completed!")
¿Qué se imprime primero?:
implicit val ec: ExecutionContext = ...
def ejecutarFuturo(): Future[Int] = Future {
Thread.sleep(2000)
println("Future completed!")
4+5
}
ejecutarFuturo()
println("Completed!")
¿Qué se imprime primero?:
> Completed!
> // suceden cerca de 2 segs
> Future completed!
Tienen una API muy rica que permite:
Tienen una API muy rica que permite:
Tienen una API muy rica que permite:
Tienen una API muy rica que permite:
Tienen una API muy rica que permite:
class Future[T] {
def isCompleted: Boolean
def value: Option[Try[T]]
}
El resultado de estas funciones depende de cuando se llamen:
class Future[T] {
def isCompleted: Boolean
def value: Option[Try[T]]
}
El resultado de estas funciones depende de cuando se llamen:
None // <- No ha terminado
Some(Success(t)) // <- Terminó exitosamente con valor t
Some(Failure(throwable)) // <- Terminó fallidamente con un throwable
value puede retornar uno de tres posibles resultados (dependiendo del momento en el que se llamen):
class Future[T] {
def isCompleted: Boolean
def value: Option[Try[T]]
}
None // <- No ha terminado
Some(Success(t)) // <- Terminó exitosamente con valor t
Some(Failure(throwable)) // <- Terminó fallidamente con un throwable
El resultado de estas funciones depende de cuando se llamen:
value puede retornar uno de tres posibles resultados (dependiendo del momento en el que se llamen):
Estas funciones NO deberían usarse.
Para "manipular" el valor del futuro hay otras
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
Sirven para mandar a ejecutar cierto pedazo de código una vez se haya resuelto el Futuro con éxito o falla
(onSuccess y onFailure están implementados reusando onComplete)
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
¿Por qué son funciones imperativas?
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
¿Por qué son funciones imperativas?
¿Si transformo el valor del futuro puedo retornar el resultado fácilmente?
¿Si transformo el resultado haciendo otra llamada asíncrona puedo retornar ése resultado?
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
¿Por qué son funciones imperativas?
¿Si transformo el valor del futuro puedo retornar el resultado fácilmente?
¿Si transformo el resultado haciendo otra llamada asíncrona puedo retornar ése resultado?
Funciones que no retornan nada (Unit) no son una buena base para la composición
class Future[A] {
def map[B](f: A => B)(implicit ec: ExecutionContext): Future[B] = {
...
}
}
¿Qué pasa si uno quiere empezar con algo asíncrono y necesita continuar con el resultado de otra acción que también es asíncrona?
¿Qué pasa si uno quiere empezar con algo asíncrono y necesita continuar con el resultado de otra acción que también es asíncrona?
def userTrackIds(userId: UserId): Future[List[TrackId]] = { ... }
def tracks(trackIds: List[TrackId]): Future[List[Track]] = { ... }
def userTracks(userId: UserId): Future[List[Track]] = ???
Por ejemplo, ¿cómo obtener los tracks de un usuario dadas las siguientes funciones?:
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
}
}
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
¿Una acción asíncrona que devuelve otra acción asíncrona?
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
¿Una acción asíncrona que devuelve otra acción asíncrona?
No tiene mucho sentido
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
¿Una acción asíncrona que devuelve otra acción asíncrona?
No tiene mucho sentido
¿Entonces?
La firma de flatMap es es algo como:
class Future[A] {
def flatMap[B](f: A => Future[B])(implicit ec: ExecutionContext): Future[B] = {
...
}
}
Es decir, a flatMap se le debe pasar una función que devuelva un resultado asíncrono: otro Future
La firma de flatMap es es algo como:
class Future[A] {
def flatMap[B](f: A => Future[B])(implicit ec: ExecutionContext): Future[B] = {
...
}
}
Es decir, a flatMap se le debe pasar una función que devuelva un resultado asíncrono: otro Future
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).flatMap { trackIds =>
tracks(trackIds)
} // <- AHORA SI COMPILA!!!!!!!!!!
}
def userTracks(userId: UserId): Future[List[Track]] = {
for {
trackIds <- userTrackIds(userId)
userTracks <- tracks(trackIds)
} yield userTracks
}
O usando un for-comprehension:
Pueden imaginar que `<-` es un operador dentro de los for-comprehension que sirve para "extraer" el valor del Futuro
Siendo e0 una expresión que es de tipo Future y e una expresión que usa p0:
e0.map { p0 => e }
Es lo mismo que:
for {
p0 <- e0
} yield e
e0.flatMap { p0 =>
e1.flatMap { p1 =>
e2.flatMap { p2 =>
...
en.map { pn => e }
}
}
}
Es lo mismo que:
for {
p0 <- e0
p1 <- e1
...
pn <- en
} yield e
val f1: Future[Int] = ejecutarF1() // <- Se empieza a ejecutar
val f2: Future[String] = ejecutarF2() // <- Se empieza a ejecutar
val f3: Future[(Int,String)] = for {
n <- f1 // <- Se está ejecutando en paralelo a f2
s <- f2 // <- Se está ejecutando en paralelo a f1
} yield (n+1, "Hola,"+s) // Solo se ejecuta cuando f1 y f2 acaben
flatMap también se puede usar para reunir resultados de varios Futures que se ejecutan en paralelo:
val f1: Future[Int] = ejecutarF1() // <- Se empieza a ejecutar
val f2: Future[String] = ejecutarF2() // <- Se empieza a ejecutar
val f3: Future[(Int,String)] = for {
n <- f1 // <- Se está ejecutando en paralelo a f2
s <- f2 // <- Se está ejecutando en paralelo a f1
} yield (n+1, "Hola,"+s) // Solo se ejecuta cuando f1 y f2 acaben
flatMap también se puede usar para reunir resultados de varios Futures que se ejecutan en paralelo:
En cambio lo siguiente no se ejecutaría en paralelo:
val f3: Future[(Int,String)] = for {
n <- ejecutarF1() // <- Se empieza a ejecutar
s <- ejecutarF2() // <- Solo se empieza a ejecutar cuando se haya resuelto "n"
} yield (n+1, "Hola,"+s) // Solo se ejecuta cuando f1 y f2 acaben
Sirven para convertir un error del futuro en un valor
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"
¿Qué quiere decir U >: T?
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"
¿Qué quiere decir U >: T?
U es un supertipo de T.
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"
¿Qué quiere decir U >: T?
U es un supertipo de T.
U es un tipo igual a T o más general que T
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
¿De qué tipo es f2?
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
¿De qué tipo es f2?
Del primer tipo papá en común entre el caso exitoso y el del recover
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
¿De qué tipo es f2?
Del primer tipo papá en común entre el caso exitoso y el del recover
Future[FileExecutionResult]
def getSomeResource(ipInstancia: String): Future[HttpResponse] = ???
val ipInstancia1 = ???
val ipInstancia2 = ???
val retriedResponse: Future[HttpResponse] = getSomeResource(ipInstancia1).recoverWith {
case _: TimeoutException => getSomeResource(ipInstancia2)
}
Permite usar un resultado asíncrono para recuperar un futuro fallido
Sirven para envolver una constante dentro de un Futuro exitóso o fallido
Sirven para envolver una constante dentro de un Futuro exitóso o fallido
def obtenerArgumentosServicio(): Future[Args] = ???
def consumirServicio(args: Args): Future[Resp] = ???
def argumentosValidos(args: Args): Boolean = ???
obtenerArgumentosServicio().flatMap( args =>
if(argumentosValidos(args))
consumirServicio(args)
else
Future.failed(new InvalidArgumentException("Argumentos inválidos"))
}
Sirven para envolver una constante dentro de un Futuro exitóso o fallido
Llamarlos no gasta un thread, a diferencia de Future { ... }
class SamlLogoutNotificatorActor(notifier: SamlLogoutNotifier) extends Actor {
override def receive: Receive = {
case notifyLogout: NotifyLogout => {
val originalSender = sender
val currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
@volatile var sessionsResultCodes = List[SessionStatus.Value]()
def responseToSender() = blocking {
if (sessionsResultCodes.size == sessions.size) {
val allSessionsClosed = sessionsResultCodes.forall(sessionStatus => sessionStatus == SessionStatus.CLOSED)
originalSender ! LogoutNotified(allSessionsClosed)
}
}
implicit val ec: ExecutionContext = context.dispatcher
sessions.foreach(session => {
if (currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest)
.onComplete {
case Success(response) => {
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.CLOSED
} else {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.ACTIVE
}
responseToSender()
}
case Failure(t) => {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.ACTIVE
responseToSender()
}
}
} else {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.CLOSED
responseToSender()
}
})
}
}
}
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
¿Cómo podemos reunir los resultados de múltiples hilos en un sólo sitio?
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
¿Cómo podemos reunir los resultados de múltiples hilos en un sólo sitio?
¿Con un actor que tenga una lista y recibimos el resultado de cada hilo como un mensaje?
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
¿Cómo podemos reunir los resultados de múltiples hilos en un sólo sitio?
¿Con un actor que tenga una lista y recibimos el resultado de cada hilo como un mensaje?
No es necesario
Buscamos una función con tipo:
List[Future[SessionStatus]] => Future[List[SessionStatus]]
Buscamos una función con tipo:
List[Future[SessionStatus]] => Future[List[SessionStatus]]
¡La librería estándar ya la tiene!
def sequence[A](list: List[Future[A]])(implicit executor: ExecutionContext): Future[List[A]]
Buscamos una función con tipo:
List[Future[SessionStatus]] => Future[List[SessionStatus]]
¡La librería estándar ya la tiene!
def sequence[A](list: List[Future[A]])(implicit executor: ExecutionContext): Future[List[A]]
(En realidad la firma es mucho más complicada):
class SamlLogoutNotificatorActor(notifier: SamlLogoutNotifier) extends Actor {
override def receive: Receive = {
case notifyLogout: NotifyLogout =>
val originalSender = sender
val currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
implicit val ec: ExecutionContext = context.dispatcher
val resultCodesFutures = sessions.map { session =>
if(currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest).map { response =>
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
SessionStatus.CLOSED
} else {
SessionStatus.ACTIVE
}
} recover {
case t => SessionStatus.ACTIVE
}
} else {
Future.successful( SessionStatus.CLOSED )
}
}
Future.sequence(resultCodesFutures).onSuccess { sessionsResultCodes =>
val allSessionsClosed = sessionsResultCodes.forall {
status => status == SessionStatus.CLOSED
}
originalSender ! LogoutNotified(allSessionsClosed)
}
}
}
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Se podría implementar reusando Future.sequence así:
def traverse[A,B](l: List[A])(f: A => Future[B])(implicit ec: ExecutionContext): Future[List[B]] = {
sequence(l.map(f))
}
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Se podría implementar reusando Future.sequence así:
Future.sequence también se puede implementar usando Future.traverse.
def traverse[A,B](l: List[A])(f: A => Future[B])(implicit ec: ExecutionContext): Future[List[B]] = {
sequence(l.map(f))
}
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Se podría implementar reusando Future.sequence así:
Future.sequence también se puede implementar usando Future.traverse.
Las implementaciones de la librería estándar no reusan nada para evitar recorrer la lista más de una vez
def traverse[A,B](l: List[A])(f: A => Future[B])(implicit ec: ExecutionContext): Future[List[B]] = {
sequence(l.map(f))
}
class SamlLogoutNotificatorActor(notifier: SamlLogoutNotifier) extends Actor {
override def receive: Receive = {
case notifyLogout: NotifyLogout =>
val originalSender = sender
val currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
implicit val ec: ExecutionContext = context.dispatcher
Future.traverse(sessions) { session =>
if(currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest).map { response =>
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
SessionStatus.CLOSED
} else {
SessionStatus.ACTIVE
}
} recover {
case t => SessionStatus.ACTIVE
}
} else {
Future.successful( SessionStatus.CLOSED )
}
}.onSuccess { sessionsResultCodes =>
val allSessionsClosed = sessionsResultCodes.forall {
status => status == SessionStatus.CLOSED
}
originalSender ! LogoutNotified(allSessionsClosed)
}
}
}
def notifyLogout(
notifyLogout: NotifyLogout,
notifier: SamlLogoutNotifier)(implicit val ec: ExecutionContext): Future[Boolean] = {
currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
sessions.traverse { session =>
if(currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest).map { response =>
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
SessionStatus.CLOSED
} else {
SessionStatus.ACTIVE
}
} recover {
case t => SessionStatus.ACTIVE
}
} else {
Future.successful( SessionStatus.CLOSED )
}
}.map { sessionsResultCodes =>
sessionsResultCodes.forall {
status => status == SessionStatus.CLOSED
}
}
}
Los Futuros funcionan abstrayendo el encadenamiento de callbacks.
La base de todo es onComplete
La interfaz de ExecutionContext:
Otras funciones combinadoras utilizan onComplete y Promesas:
Otras funciones combinadoras utilizan onComplete y Promesas:
Otras funciones combinadoras utilizan onComplete y Promesas:
Si alguna vez han escrito un onComplete dentro de otro onComplete...
Básicamente:
Básicamente:
Básicamente:
Básicamente:
scala> def f(): Future[Int] = Future {
| Thread.sleep(2100)
| println("Terminado!")
| 2
| }
f: ()scala.concurrent.Future[Int]
scala> Await.result(f(),2000 millis)
java.util.concurrent.TimeoutException: Futures timed out after [2000 milliseconds]
at scala.concurrent.impl.Promise$DefaultPromise.ready(Promise.scala:219)
at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:223)
at scala.concurrent.Await$$anonfun$result$1.apply(package.scala:116)
at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:53)
at scala.concurrent.Await$.result(package.scala:116)
... 33 elided
Terminado!
scala>
Hacer esto:
future.map(f).map(g).map(h)
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
future.map(f).map(g).map(h)
Manda a ejecutar asíncronamente f en un thread y una vez terminado manda a ejecutar asíncronamente g en otro thread y ...
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
future.map(f).map(g).map(h)
Manda a ejecutar asíncronamente f en un thread y una vez terminado manda a ejecutar asíncronamente g en otro thread y ...
future.map(f andThen g andThen h)
Manda a ejecutar asíncronamente una sola función en un thread
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
future.map(f).map(g).map(h)
Manda a ejecutar asíncronamente f en un thread y una vez terminado manda a ejecutar asíncronamente g en otro thread y ...
future.map(f andThen g andThen h)
Manda a ejecutar asíncronamente una sola función en un thread
3 momentos de asincronía
1 momento de asincronía
¿Qué hace el Future de scalaz?
Separar operaciones bloqueantes en un su propio pool de threads
We learned a few lessons along the way. The actors that syndicated content were initially configured to execute in a Future using Play’s default execution context. This meant any network anomaly could result in both syndication and user requests being blocked because the controllers also shared the same execution context. The solution was simple — have the Futures execute in a different execution context (...)
Given the fact that JDBC is blocking thread pools can be sized to the # of connections available to a db pool assuming that the thread pool is used exclusively for database access. Any lesser amount of threads will not consume the number of connections available. Any more threads than the number of connections available could be wasteful given contention for the connections.
¿Cómo debería ser el pool de threads de la base de datos?
Note: Both Action.apply and Action.async create Action objects that are handled internally in the same way. There is a single kind of Action, which is asynchronous, and not two kinds (a synchronous one and an asynchronous one). The.async builder is just a facility to simplify creating actions based on APIs that return a Future, which makes it easier to write non-blocking code.
No por que se esté usando Action.async se está ganando algo
Problema: modificar el estado de un actor según el resultado de un futuro
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => modificarEstado(response)
case Failure(error) => manejarError(error)
}
}
}
Problema: modificar el estado de un actor según el resultado de un futuro
Una aproximación inicial:
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => modificarEstado(response)
case Failure(error) => manejarError(error)
}
}
}
¿Qué pasa si llegan dos mensajes Msg? Uno va a ser procesado después del otro, pero los Futuros se podrían ejecutar concurrentemente
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => modificarEstado(response)
case Failure(error) => manejarError(error)
}
}
}
¿Qué pasa si llegan dos mensajes Msg? Uno va a ser procesado después del otro, pero los Futuros se podrían ejecutar concurrentemente
¡La idea de los actores es proteger el estado de modificaciones concurrentes!
¿Qué hay de usar context.become?
Una segunda aproximación (¡bloquear!):
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
val timeout: FiniteDuration = ???
def receive: Receive = {
case msg: Msg =>
val responseFuture = consulta(msg)
try {
val response = Await.result(responseFuture, timeout)
modificarEstado(response)
} catch {
case error => manejarError(error)
}
}
}
Una segunda aproximación (¡bloquear!):
¿Es tan malo bloquear en este caso?
¿Es tan malo bloquear en este caso?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
¿Es tan malo bloquear en este caso?
¿Cuántos threads se están gastando por cada mensaje?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
¿Es tan malo bloquear en este caso?
¿Cuántos threads se están gastando por cada mensaje?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
¿Es tan malo bloquear en este caso?
¿Cuántos threads se están gastando por cada mensaje?
¿Se pueden gastar menos threads?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
Complicando un poco las cosas:
class MiActor extends Actor {
case class FSuccess(response: Response)
case class FFailure(error: Throwable)
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => self ! FSuccess(response)
case Failure(error) => self ! FFailure(error)
}
case FSuccess(response) =>
modificarEstado(response)
case FFailure(error) =>
manejarError(error)
}
}
Complicando un poco las cosas:
class MiActor extends Actor with Stash {
case class FSuccess(response: Response)
case class FFailure(error: Throwable)
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => self ! FSuccess(response)
case Failure(error) => self ! FFailure(error)
}
become(esperandoFuturo)
}
def esperandoFuturo: Receive = {
case msg: Msg => stash()
case FSuccess(response) =>
modificarEstado(response); unstashAll(); become(receive)
case FFailure(error) =>
manejarError(error); unstashAll(); become(receive)
}
}
Si se necesita garantizar orden en el procesamiento de los mensajes:
¿Cuántos threads se gastan en este caso?
¿Cuántos threads se gastan en este caso?
Cuando llega un mensaje de tipo Msg:
¿Cuántos threads se gastan en este caso?
Cuando llega un mensaje de tipo Msg:
¿Cuántos threads se gastan en este caso?
Cuando llega un mensaje de tipo Msg:
Con esta solución, en un momento dado, se pueden estar gastando a lo sumo un solo thread (el del Futuro). (Esto ignorando ese pequeño momento en el que el actor está iniciando el Futuro)
Si representa una computación asíncrona...
... pero no tiene ninguno de los combinadores funcionales como map, flatMap, etc...
ListenableFuture de Guava
ListenableFuture de Guava
Una mejor alternativa. Futuros de Akka:
promise
.then(doSomething) // `doSomething` puede ser una función "síncrona" --> map
.then(doSomethingElse); // `doSomethingElse` puede devolver una promesa --> flatMap
El método .then recibe 2 funciones (la segunda es opcional):
promise
.then(null, handle) // `handle` puede ser una función "síncrona" --> recover
.then(null, handleOther); // `handleOther` puede devolver una promesa --> recoverWith
promise
.then(
function success(data) {
console.log(data);
})
.catch(function error(msg) {
console.error(msg);
});
promise
.then(
function success(data) {
console.log(data);
},
function error(msg) {
console.error(msg);
});
El método .then recibe 2 funciones (la segunda es opcional):
$q.all({
foo: promiseFoo,
bar: promiseBar,
baz: promiseBaz
}).then(function(results) {
console.log(results.foo);
console.log(results.bar);
console.log(results.baz);
});
$q.all([promiseOne, promiseTwo, promiseThree])
.then(function(results) {
console.log(results[0], results[1], results[2]);
});
¡Incluso existe el equivalente de Future.sequence!:
También sirve sobre objetos cuyos atributos son promesas:
Blog post We have a problem with promises
Facebook's Haxl (2014)
Todo esto sin perder modularidad
Con una mónada que:
Con una mónada que:
Pero por debajo se acumulan consultas individuales para usar un API en batch
Pero por debajo se cachean consultas previas
A diferencia de los Futuros no se empezaría a ejecutar apenas uno lo define
En vez de un Future[A] uno busca construir un Fetch[A]
Para hacer esto basta utilizar un API que ya conocemos: map, flatMap, traverse/sequence, recover, recoverWith, etc...
Futurice
Todavía no son Open Source
Futurice
Todavía no son Open Source
No provee cacheo
Soundcloud
Open Source