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

93

Estoy leyendo SIP-14 y el concepto de Futuretiene mucho sentido y es fácil de entender. Pero tengo 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? ¿Son Futurey Promiseno dos tipos diferentes?

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

    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 este y el ejemplo dado y qué hace necesaria una Promesa?

xiefei
fuente
En el primer ejemplo, continueDoingSomethingUnrelated () evalúa después de produceSomething () en el mismo hilo.
senia
1
Para responder a la pregunta # 1, sí Futurey Promiseson dos tipos separados, pero como puede ver en github.com/scala/scala/blob/master/src/library/scala/concurrent/… esta Promiseimplementación particular también se extiende Future.
Dylan

Respuestas:

118

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 suceda ese evento. Por lo tanto, es el punto final de lectura o salida de un cálculo; es algo de lo que recupera un valor.

Una promesa es, por analogía, el lado de la escritura del cálculo. Creas una promesa que es el lugar donde colocarás el resultado del cálculo y de esa promesa obtienes un futuro que se utilizará para leer el resultado que se puso en la promesa. Cuando complete una Promesa, ya sea por fracaso o por éxito, activará todo el comportamiento que se adjuntó al Futuro asociado.

Respecto a tu primera pregunta, ¿cómo puede ser que por una promesa p tenemos p.future == p. Puede imaginar esto como un búfer de un solo elemento: un contenedor que inicialmente está vacío y puede almacenar un valor que se convertirá en su contenido para siempre. Ahora, dependiendo de su punto de vista, esto es tanto una promesa como un futuro. Es una promesa para alguien que tenga la intención de escribir el valor en el búfer. Es un futuro para alguien que espera que ese valor se ponga en el búfer.

Específicamente, para la API concurrente de Scala, si echas un vistazo al rasgo Promise aquí , puedes ver cómo se implementan los métodos del objeto complementario Promise:

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, las implementaciones de promesas, DefaultPromise y KeptPromise se pueden encontrar aquí . Ambos extienden un pequeño rasgo básico 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
}

Para que pueda ver lo que quieren decir p.future == p.

DefaultPromisees el búfer al que me refería anteriormente, mientras que KeptPromisees un búfer con el valor puesto desde su creación.

Con respecto a su ejemplo, el bloque futuro que usa allí en realidad crea una promesa detrás de escena. Echemos un vistazo a la definición de futurede aquí :

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

Siguiendo la cadena de métodos, terminas en el 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 productor se vierte en una promesa.

EDICIÓN POSTERIOR :

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

Pero si necesita implementar su propia API asincrónica, tendrá que comenzar a trabajar con ellos. Suponga que necesita implementar un cliente HTTP asíncrono 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
    }
Marius Danila
fuente
3
@xiefei El caso de uso de Promises debe estar en el código de implementación. Futurees algo agradable de solo lectura que puede exponer al código del cliente. Además, la Future.future{...}sintaxis a veces puede resultar complicada.
Dylan
11
Puedes verlo así: no puedes tener un futuro sin una promesa. Un futuro no puede devolver un valor si no hay una promesa que se complete en primer lugar. Las promesas no son opcionales, son la parte escrita obligatoria de un futuro. No se puede trabajar solo con futuros porque no habría nadie que les proporcione el valor de retorno.
Marius Danila
4
Creo que veo lo que quieres decir con usos en el mundo real: he actualizado mi respuesta para darte un ejemplo.
Marius Danila
2
@Marius: Teniendo en cuenta el ejemplo del mundo real dado, ¿qué pasa si makeHTTPCall se implementa así: def makeHTTPCall(request: Request): Future[Response] = { Future { registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) response }) } }
puneetk
1
@puneetk, entonces tendrás futuro, que se completa justo después de registerOnCompleteCallback()terminar. Además, no regresa Future[Response]. Vuelve en su Future[registerOnCompleteCallback() return type]lugar.
Evgeny Veretennikov