Quali sono i casi d'uso di scala.concurrent.Promise?


93

Sto leggendo SIP-14 e il concetto di Futureha perfettamente senso e facile da capire. Ma hai due domande su Promise:

  1. Il SIP dice Depending on the implementation, it may be the case that p.future == p. Come può essere questo? Sono Futuree Promisenon due tipi diversi?

  2. Quando dovremmo usare un Promise? Il producer and consumercodice di esempio :

    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()
        }
    }

è facile da leggere ma abbiamo davvero bisogno di scrivere così? Ho provato a implementarlo solo con Future e senza Promise in questo modo:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

val consumer = future {
  f onSuccess {
    case r => doSomethingWithResult()
  }
}

Qual è la differenza tra questo e l'esempio fornito e cosa rende necessaria una promessa?


Nel primo esempio continueDoingSomethingUnrelated () valuta dopo produceSomething () nello stesso thread.
senia

1
Per rispondere alla domanda n. 1, sì Futuree Promisesono due tipi separati, ma come puoi vedere da github.com/scala/scala/blob/master/src/library/scala/concurrent/… questa particolare Promiseimplementazione si estende Futureanche.
Dylan

Risposte:


118

La promessa e il futuro sono concetti complementari. Il futuro è un valore che verrà recuperato, beh, in futuro e puoi fare cose con esso quando si verifica quell'evento. È quindi il punto finale di lettura o di uscita di un calcolo: è qualcosa da cui si recupera un valore.

A Promise è, per analogia, il lato scritto del calcolo. Crei una promessa che è il luogo in cui metti il ​​risultato del calcolo e da quella promessa ottieni un futuro che verrà utilizzato per leggere il risultato che è stato inserito nella promessa. Quando completerai una Promessa, per fallimento o successo, attiverai tutto il comportamento che era associato al Futuro associato.

Per quanto riguarda la tua prima domanda, come può essere che per una promessa p che abbiamo p.future == p. Puoi immaginarlo come un buffer di un singolo elemento: un contenitore inizialmente vuoto e successivamente puoi memorizzare un valore che diventerà il suo contenuto per sempre. Ora, a seconda del tuo punto di vista, questa è sia una promessa che un futuro. È una promessa per qualcuno che intende scrivere il valore nel buffer. È un futuro per qualcuno che aspetta che quel valore venga messo nel buffer.

In particolare, per l'API simultanea di Scala, se dai un'occhiata al tratto Promise qui puoi vedere come sono implementati i metodi dall'oggetto companion 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))

}

Ora, quelle implementazioni delle promesse, DefaultPromise e KeptPromise possono essere trovate qui . Entrambi estendono un piccolo tratto di base che sembra avere lo stesso nome, ma si trova in un pacchetto diverso:

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

Quindi puoi vedere cosa intendono p.future == p.

DefaultPromiseè il buffer a cui mi riferivo sopra, mentre KeptPromiseè un buffer con il valore inserito dalla sua stessa creazione.

Per quanto riguarda il tuo esempio, il blocco futuro che usi lì crea effettivamente una promessa dietro le quinte. Diamo un'occhiata alla definizione di futurea qui :

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

Seguendo la catena dei metodi si finisce nel Futuro impl .

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
  }
}

Quindi, come puoi vedere, il risultato che ottieni dal tuo blocco produttore viene riversato in una promessa.

MODIFICA SUCCESSIVA :

Per quanto riguarda l'uso nel mondo reale: la maggior parte delle volte non gestirai direttamente le promesse. Se utilizzerai una libreria che esegue calcoli asincroni, lavorerai solo con i futures restituiti dai metodi della libreria. Le promesse sono, in questo caso, create dalla libreria: stai solo lavorando con la fine della lettura di ciò che fanno questi metodi.

Ma se hai bisogno di implementare la tua API asincrona dovrai iniziare a lavorare con loro. Supponiamo di dover implementare un client HTTP asincrono sopra, diciamo, Netty. Quindi il tuo codice avrà un aspetto simile a questo

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

3
@xiefei Il caso d'uso per Promises dovrebbe essere nel codice di implementazione. Futureè una cosa carina di sola lettura che puoi esporre al codice client. Inoltre, la Future.future{...}sintassi a volte può essere complicata.
Dylan

11
Puoi vederlo così: non puoi avere un futuro senza una promessa. Un futuro non può restituire un valore se non c'è una promessa che viene completata in primo luogo. Le promesse non sono opzionali, sono il lato scritto obbligatorio di un futuro. Non puoi lavorare solo con i futures perché non ci sarebbe nessuno a fornire loro il valore di ritorno.
Marius Danila

4
Penso di capire cosa intendi per usi nel mondo reale: ho aggiornato la mia risposta per darti un esempio.
Marius Danila

2
@Marius: Considerando l'esempio del mondo reale fornito, e se makeHTTPCall fosse implementato in questo modo: def makeHTTPCall(request: Request): Future[Response] = { Future { registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) response }) } }
puneetk

1
@puneetk allora avrai futuro, che si completa subito dopo aver registerOnCompleteCallback()finito. Inoltre, non ritorna Future[Response]. Ritorna Future[registerOnCompleteCallback() return type]invece.
Evgeny Veretennikov
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.