scala concurrency

¿Cuáles son los casos de uso de scala.concurrent.Promise?



concurrency (1)

Estoy leyendo SIP-14 y el concepto de Future tiene perfecto sentido y es fácil de entender. Pero tiene dos preguntas sobre Promise :

  1. El SIP dice Depending on the implementation, it may be the case that p.future == p . ¿Cómo puede ser esto? ¿ Future and Promise no son dos tipos diferentes?

  2. ¿Cuándo deberíamos usar una Promise ? El ejemplo de producer and consumer código de producer and consumer :

    import scala.concurrent.{ future, promise } val p = promise[T] val f = p.future val producer = future { val r = produceSomething() p success r continueDoingSomethingUnrelated() } val consumer = future { startDoingSomething() f onSuccess { case r => doSomethingWithResult() } }

es fácil de leer, pero ¿realmente necesitamos escribir así? Intenté implementarlo solo con Future y sin Promise como este:

val f = future { produceSomething() } val producer = future { continueDoingSomethingUnrelated() } startDoingSomething() val consumer = future { f onSuccess { case r => doSomethingWithResult() } }

¿Cuál es la diferencia entre esto y el ejemplo dado y qué hace necesaria una Promesa?


La promesa y el futuro son conceptos complementarios. El futuro es un valor que se recuperará, bueno, en algún momento en el futuro y puedes hacer cosas con él cuando ocurra ese evento. Es, por lo tanto, el punto final de lectura o salida de un cálculo; es algo de lo que se recupera un valor.

Una promesa es, por analogía, el lado de escritura de la computación. Usted crea una promesa que es el lugar donde colocará el resultado del cálculo y de esa promesa obtendrá un futuro que se usará para leer el resultado que se incluyó en la promesa. Cuando complete una Promesa, ya sea por fracaso o por éxito, desencadenará todo el comportamiento que se adjuntó al futuro asociado.

Con respecto a su primera pregunta, ¿cómo puede ser que para una promesa p tengamos p.future == p . Puede imaginar esto como un búfer de un solo elemento: un contenedor que está inicialmente vacío y puede almacenar después un valor que se convertirá en su contenido para siempre. Ahora, dependiendo de tu punto de vista, esto es a la vez Promesa y Futuro. Es una promesa para alguien que tiene la intención de escribir el valor en el búfer. Es un futuro para alguien que espera que ese valor se coloque en el búfer.

Específicamente, para la API concurrente de Scala, si observa el rasgo Promesa here , puede ver cómo se implementan los métodos del objeto complementario Promesa:

object Promise { /** Creates a promise object which can be completed with a value. * * @tparam T the type of the value in the promise * @return the newly created `Promise` object */ def apply[T](): Promise[T] = new impl.Promise.DefaultPromise[T]() /** Creates an already completed Promise with the specified exception. * * @tparam T the type of the value in the promise * @return the newly created `Promise` object */ def failed[T](exception: Throwable): Promise[T] = new impl.Promise.KeptPromise[T](Failure(exception)) /** Creates an already completed Promise with the specified result. * * @tparam T the type of the value in the promise * @return the newly created `Promise` object */ def successful[T](result: T): Promise[T] = new impl.Promise.KeptPromise[T](Success(result)) }

Ahora, esas implementaciones de promesas, DefaultPromise y KeptPromise se pueden encontrar here . Ambos extienden un pequeño rasgo base que tiene el mismo nombre, pero se encuentra en un paquete diferente:

private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with scala.concurrent.Future[T] { def future: this.type = this }

Entonces puedes ver lo que quieren decir con p.future == p .

DefaultPromise es el búfer al que me refería anteriormente, mientras que KeptPromise es un búfer con el valor puesto desde su propia creación.

Con respecto a su ejemplo, el bloque futuro que utiliza allí en realidad crea una promesa detrás de la escena. Veamos la definición de future here :

def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body)

Al seguir la cadena de métodos, terminas en la impl.Future :

private[concurrent] object Future { class PromiseCompletingRunnable[T](body: => T) extends Runnable { val promise = new Promise.DefaultPromise[T]() override def run() = { promise complete { try Success(body) catch { case NonFatal(e) => Failure(e) } } } } def apply[T](body: =>T)(implicit executor: ExecutionContext): scala.concurrent.Future[T] = { val runnable = new PromiseCompletingRunnable(body) executor.execute(runnable) runnable.promise.future } }

Entonces, como puede ver, el resultado que obtiene de su bloque de productores se vierte en una promesa.

EDICION POSTERIOR :

En cuanto al uso en el mundo real: la mayoría de las veces no se ocupará de las promesas directamente. Si va a utilizar una biblioteca que realiza cálculos asíncronos, simplemente trabajará con los futuros devueltos por los métodos de la biblioteca. Las promesas son, en este caso, creadas por la biblioteca; usted solo está trabajando con el fin de la lectura de lo que hacen esos métodos.

Pero si necesita implementar su propia API asíncrona, tendrá que empezar a trabajar con ellos. Supongamos que necesita implementar un cliente HTTP asincrónico además de, digamos, Netty. Entonces su código se verá algo así

def makeHTTPCall(request: Request): Future[Response] = { val p = Promise[Response] registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) p success response }) p.future }