scala asynchronous future async-await

Scala async/await y paralelización



asynchronous future (3)

Estoy aprendiendo sobre los usos de async / await en Scala. He leído esto en https://github.com/scala/async

Teóricamente, este código es asíncrono (no bloqueante), pero no está paralelizado:

def slowCalcFuture: Future[Int] = ... def combined: Future[Int] = async { await(slowCalcFuture) + await(slowCalcFuture) } val x: Int = Await.result(combined, 10.seconds)

mientras que este otro está paralelizado:

def combined: Future[Int] = async { val future1 = slowCalcFuture val future2 = slowCalcFuture await(future1) + await(future2) }

La única diferencia entre ellos es el uso de variables intermedias. ¿Cómo puede esto afectar la paralelización?


Dado que es similar a async & await en C #, tal vez pueda proporcionarle alguna información. En C #, es una regla general que la Task que se puede esperar se debe devolver ''caliente'', es decir, ya se está ejecutando. Supongo que es lo mismo en Scala, donde el Future devuelto por la función no tiene que iniciarse explícitamente, sino que simplemente se está ''ejecutando'' después de ser llamado. Si no es el caso, la siguiente es una especulación pura (y probablemente no verdadera).

Analicemos el primer caso:

async { await(slowCalcFuture) + await(slowCalcFuture) }

Llegamos a ese bloque y golpeamos el primero en espera:

async { await(slowCalcFuture) + await(slowCalcFuture) ^^^^^ }

Ok, entonces estamos esperando asíncronamente que termine ese cálculo. Cuando haya terminado, ''avanzaremos'' con el análisis del bloque:

async { await(slowCalcFuture) + await(slowCalcFuture) ^^^^^ }

En segundo lugar, esperamos, así que estamos esperando asíncronamente que el segundo cálculo termine. Una vez hecho esto, podemos calcular el resultado final agregando dos enteros.

Como puede ver, nos estamos moviendo paso a paso en la espera, esperando que llegue el Future , ya que vienen uno por uno.

Echemos un vistazo al segundo ejemplo:

async { val future1 = slowCalcFuture val future2 = slowCalcFuture await(future1) + await(future2) }

OK, entonces esto es lo que (probablemente) suceda:

async { val future1 = slowCalcFuture // >> first future is started, but not awaited val future2 = slowCalcFuture // >> second future is started, but not awaited await(future1) + await(future2) ^^^^^ }

Entonces estamos esperando el primer Future , pero ambos futuros se están ejecutando actualmente. Cuando regrese el primero, el segundo ya se habrá completado (para que tengamos el resultado disponible de inmediato) o tendremos que esperar un poco más.

Ahora está claro que el segundo ejemplo ejecuta dos cálculos en paralelo, luego espera a que ambos finalicen. Cuando ambos están listos, regresa. El primer ejemplo ejecuta los cálculos de forma no bloqueante, pero secuencialmente.


En el primer caso, crea un nuevo hilo para ejecutar un futuro lento y esperarlo en una sola llamada. Entonces, la invocación del segundo futuro lento se realiza después de que se completa el primero.

En el segundo caso, cuando se val future1 = slowCalcFuture , efectivamente crea un nuevo hilo, pasa el puntero a la función "slowCalcFuture" al hilo y dice "ejecutarlo por favor". Se necesita tanto tiempo como sea necesario para obtener una instancia de subproceso del grupo de subprocesos y pasar un puntero a una función a la instancia de subproceso. Que se puede considerar instantáneo. Entonces, como val future1 = slowCalcFuture se traduce en operaciones "get thread and pass pointer", se completa en un instante y la siguiente línea se ejecuta sin demora val future2 = slowCalcFuture . También está programado que Feauture 2 se ejecute sin demora.

La diferencia fundamental entre val future1 = slowCalcFuture y val future1 = slowCalcFuture await(slowCalcFuture) es la misma que entre pedirle a alguien que le haga un café y esperar a que su café esté listo. Pedir toma 2 segundos: lo cual es necesario para decir frase: "¿podrías hacerme un café, por favor?". Pero esperar a que el café esté listo tomará 4 minutos.

Posible modificación de esta tarea podría estar esperando la primera respuesta disponible. Por ejemplo, desea conectarse a cualquier servidor en un clúster. Usted emite solicitudes para conectarse a cada servidor que conoce, y el primero que responde, será su servidor. Puede hacer esto con: Future.firstCompletedOf(Array(slowCalcFuture, slowCalcFuture))


la respuesta de Patryk es correcta si es un poco difícil de seguir. Lo más importante para entender sobre async / await es que es solo otra forma de hacer flatMap Future . no hay magia de concurrencia entre bastidores. todas las llamadas dentro de un bloque asíncrono son secuenciales, incluida la espera que no bloquea realmente el hilo en ejecución, sino que envuelve el resto del bloque asíncrono en un cierre y lo pasa como una devolución de llamada al completar el Future que estamos esperando. entonces, en el primer trozo de código, el segundo cálculo no comienza hasta que el primer estado de espera se haya completado ya que nadie lo inició antes de eso.