web-dev-qa-db-fra.com

Quels sont les cas d'utilisation de scala.concurrent.Promise?

Je lis SIP-14 et le concept de Future est parfaitement logique et facile à comprendre. Mais vous avez deux questions sur Promise:

  1. Le SIP dit Depending on the implementation, it may be the case that p.future == p. Comment se peut-il? Future et Promise ne sont-ils pas deux types différents?

  2. Quand utiliser un Promise? L'exemple producer and consumer code:

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

est facile à lire mais faut-il vraiment écrire comme ça? J'ai essayé de l'implémenter uniquement avec Future et sans Promise comme ceci:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

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

Quelle est la différence entre cela et l'exemple donné et qu'est-ce qui rend une promesse nécessaire?

91
xiefei

La promesse et l'avenir sont des concepts complémentaires. L'avenir est une valeur qui sera récupérée, eh bien, dans le futur et vous pouvez faire des choses avec quand cet événement se produira. Il s'agit donc du point final de lecture ou de sortie d'un calcul - c'est quelque chose à partir duquel vous récupérez une valeur.

Une Promesse est, par analogie, le côté écriture du calcul. Vous créez une promesse qui est l'endroit où vous mettrez le résultat du calcul et à partir de cette promesse vous obtenez un avenir qui sera utilisé pour lire le résultat qui a été mis dans la promesse. Lorsque vous terminerez une promesse, que ce soit par échec ou par succès, vous déclencherez tout le comportement qui était attaché au futur associé.

Concernant votre première question, comment se fait-il que pour une promesse p nous ayons p.future == p. Vous pouvez l'imaginer comme un tampon à élément unique - un conteneur qui est initialement vide et vous pouvez ensuite stocker une valeur qui deviendra son contenu pour toujours. Maintenant, selon votre point de vue, c'est à la fois une promesse et un avenir. C'est une promesse pour quelqu'un qui a l'intention d'écrire la valeur dans le tampon. C'est un avenir pour quelqu'un qui attend que cette valeur soit placée dans le tampon.

Plus précisément, pour l'API simultanée Scala, si vous regardez le trait Promise dans ici , vous pouvez voir comment les méthodes de l'objet compagnon Promise sont implémentées:

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

}

Maintenant, ces implémentations de promesses, DefaultPromise et KeptPromise peuvent être trouvées ici . Ils étendent tous deux un petit trait de base qui se trouve avoir le même nom, mais il est situé dans un package différent:

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

Vous pouvez donc voir ce qu'ils entendent par p.future == p.

DefaultPromise est le tampon auquel je faisais référence ci-dessus, tandis que KeptPromise est un tampon avec la valeur ajoutée depuis sa création même.

En ce qui concerne votre exemple, le futur bloc que vous utilisez là-bas crée en fait une promesse dans les coulisses. Regardons la définition de future dans ici :

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

En suivant la chaîne de méthodes, vous vous retrouvez dans le 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
  }
}

Ainsi, comme vous pouvez le voir, le résultat que vous obtenez de votre bloc de producteurs est versé dans une promesse.

MODIFICATION PLUS TARD :

En ce qui concerne l'utilisation dans le monde réel: la plupart du temps, vous ne traiterez pas directement les promesses. Si vous utilisez une bibliothèque qui effectue un calcul asynchrone, alors vous travaillerez simplement avec les futurs renvoyés par les méthodes de la bibliothèque. Les promesses sont, dans ce cas, créées par la bibliothèque - vous travaillez simplement avec le côté lecture de ce que font ces méthodes.

Mais si vous devez implémenter votre propre API asynchrone, vous devrez commencer à travailler avec eux. Supposons que vous ayez besoin d'implémenter un client HTTP asynchrone sur, disons, Netty. Ensuite, votre code ressemblera un peu à ceci

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