Was sind die Anwendungsfälle von scala.concurrent.Promise?

93

Ich lese SIP-14 und das Konzept von Futuremacht vollkommen Sinn und ist leicht zu verstehen. Habe aber zwei Fragen zu Promise:

  1. Das SIP sagt Depending on the implementation, it may be the case that p.future == p. Wie kann das sein? Sind Futureund Promisenicht zwei verschiedene Typen?

  2. Wann sollten wir ein verwenden Promise? Der Beispielcode 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()
        }
    }

ist leicht zu lesen, aber müssen wir wirklich so schreiben? Ich habe versucht, es nur mit Future und ohne Promise wie folgt umzusetzen:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

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

Was ist der Unterschied zwischen diesem und dem gegebenen Beispiel und was macht ein Versprechen notwendig?

xiefei
quelle
Im ersten Beispiel wird continueDoingSomethingUnrelated () nach produzierenSomething () im selben Thread ausgewertet.
Senia
1
Um Frage 1 zu beantworten, ja Futureund es Promisegibt zwei verschiedene Typen, aber wie Sie unter github.com/scala/scala/blob/master/src/library/scala/concurrent/… sehen können Promise, erstreckt sich diese spezielle Implementierung Futureauch.
Dylan

Antworten:

118

Das Versprechen und die Zukunft sind komplementäre Konzepte. Die Zukunft ist ein Wert, der irgendwann in der Zukunft abgerufen wird, und Sie können damit etwas anfangen, wenn dieses Ereignis eintritt. Es ist daher der ausgelesene oder ausgelesene Endpunkt einer Berechnung - es ist etwas, von dem Sie einen Wert abrufen.

Ein Versprechen ist analog die Schreibseite der Berechnung. Sie erstellen ein Versprechen, an dem Sie das Ergebnis der Berechnung ablegen, und aus diesem Versprechen erhalten Sie eine Zukunft, in der Sie das Ergebnis lesen können, das in das Versprechen aufgenommen wurde. Wenn Sie ein Versprechen entweder durch Misserfolg oder Erfolg erfüllen, lösen Sie das gesamte Verhalten aus, das mit der zugehörigen Zukunft verbunden war.

In Bezug auf Ihre erste Frage, wie kann es sein, dass wir ein Versprechen haben p.future == p? Sie können sich dies wie einen Einzelelementpuffer vorstellen - einen Container, der anfangs leer ist, und Sie können Nachwörter einen Wert speichern, der für immer zum Inhalt wird. Abhängig von Ihrer Sichtweise ist dies nun sowohl ein Versprechen als auch eine Zukunft. Es ist ein Versprechen für jemanden, der beabsichtigt, den Wert in den Puffer zu schreiben. Es ist eine Zukunft für jemanden, der darauf wartet, dass dieser Wert in den Puffer gestellt wird.

Insbesondere für die gleichzeitige Scala-API können Sie anhand der Promise-Eigenschaft hier sehen, wie die Methoden aus dem Promise-Begleitobjekt implementiert werden:

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

}

Die Umsetzung der Versprechen DefaultPromise und KeptPromise finden Sie hier . Beide erweitern ein kleines Basismerkmal, das zufällig denselben Namen hat, sich jedoch in einem anderen Paket befindet:

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

So können Sie sehen, was sie bedeuten p.future == p.

DefaultPromiseist der Puffer, auf den ich mich oben bezogen habe, während KeptPromisees sich um einen Puffer handelt, dessen Wert von Anfang an eingegeben wurde.

In Bezug auf Ihr Beispiel schafft der zukünftige Block, den Sie dort verwenden, tatsächlich ein Versprechen hinter den Kulissen. Schauen wir uns die Definition der futurein hier :

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

Wenn Sie der Methodenkette folgen, gelangen Sie in die 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
  }
}

Wie Sie sehen, wird das Ergebnis, das Sie von Ihrem Produzentenblock erhalten, in ein Versprechen umgewandelt.

SPÄTER BEARBEITEN :

In Bezug auf die reale Verwendung: Meistens werden Sie nicht direkt mit Versprechungen umgehen. Wenn Sie eine Bibliothek verwenden, die asynchrone Berechnungen durchführt, arbeiten Sie nur mit den von den Methoden der Bibliothek zurückgegebenen Futures. Versprechen werden in diesem Fall von der Bibliothek erstellt - Sie arbeiten nur mit dem Leseende dieser Methoden.

Wenn Sie jedoch Ihre eigene asynchrone API implementieren müssen, müssen Sie mit ihnen arbeiten. Angenommen, Sie müssen einen asynchronen HTTP-Client zusätzlich zu Netty implementieren. Dann sieht Ihr Code ungefähr so ​​aus

    def makeHTTPCall(request: Request): Future[Response] = {
        val p = Promise[Response]
        registerOnCompleteCallback(buffer => {
            val response = makeResponse(buffer)
            p success response
        })
        p.future
    }
Marius Danila
quelle
3
@xiefei Der Anwendungsfall für Promises sollte im Implementierungscode enthalten sein. Futureist eine nette, schreibgeschützte Sache, die Sie dem Client-Code aussetzen können. Außerdem kann die Future.future{...}Syntax manchmal umständlich sein.
Dylan
11
Sie können es so sehen: Sie können keine Zukunft ohne ein Versprechen haben. Eine Zukunft kann keinen Wert zurückgeben, wenn es kein Versprechen gibt, das überhaupt erfüllt wird. Versprechen sind nicht optional, sie sind die obligatorische Schreibseite einer Zukunft. Sie können nicht nur mit Futures arbeiten, da es niemanden gibt, der ihnen den Rückgabewert liefert.
Marius Danila
4
Ich glaube, ich verstehe, was Sie unter realen Verwendungen verstehen: Ich habe meine Antwort aktualisiert, um Ihnen ein Beispiel zu geben.
Marius Danila
2
@Marius: In Anbetracht des gegebenen Beispiels aus der Praxis, was ist, wenn makeHTTPCall wie folgt implementiert wird: def makeHTTPCall(request: Request): Future[Response] = { Future { registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) response }) } }
Puneetk
1
@puneetk dann hast du Zukunft, die gleich nach registerOnCompleteCallback()Abschluss abgeschlossen ist. Auch dann , wenn es nicht zurück Future[Response]. Es kehrt Future[registerOnCompleteCallback() return type]stattdessen zurück.
Evgeny Veretennikov