Quais são os casos de uso de scala.concurrent.Promise?

93

Estou lendo SIP-14 e o conceito de Futurefaz todo o sentido e fácil de entender. Mas tenho duas perguntas sobre Promise:

  1. O SIP diz Depending on the implementation, it may be the case that p.future == p. Como isso pode ser? São Futuree Promisenão dois tipos diferentes?

  2. Quando devemos usar um Promise? O producer and consumercódigo de exemplo :

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

é fácil de ler, mas realmente precisamos escrever assim? Tentei implementá-lo apenas com Future and without Promise assim:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

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

Qual é a diferença entre este e o exemplo dado e o que torna uma promessa necessária?

xiefei
fonte
No primeiro exemplo continueDoingSomethingUnrelated () é avaliado após produzirSomething () no mesmo thread.
Senia
1
Para responder à pergunta nº 1, sim Futuree Promisesão dois tipos separados, mas como você pode ver em github.com/scala/scala/blob/master/src/library/scala/concurrent/… esta Promiseimplementação específica também se estende Future.
Dylan

Respostas:

118

A promessa e o futuro são conceitos complementares. O Futuro é um valor que será recuperado, bem, em algum momento no futuro e você pode fazer coisas com ele quando esse evento acontecer. É, portanto, o ponto final de leitura ou saída de um cálculo - é algo de onde você recupera um valor.

Uma promessa é, por analogia, o lado da escrita do cálculo. Você cria uma promessa que é o lugar onde você colocará o resultado do cálculo e dessa promessa você obterá um futuro que será usado para ler o resultado que foi colocado na promessa. Ao cumprir uma promessa, seja por fracasso ou sucesso, você acionará todo o comportamento que estava vinculado ao futuro associado.

Com relação à sua primeira pergunta, como pode ser isso para uma promessa p que temos p.future == p. Você pode imaginar isso como um buffer de item único - um contêiner que está inicialmente vazio e você pode armazenar um valor que se tornará seu conteúdo para sempre. Agora, dependendo do seu ponto de vista, isso é uma promessa e um futuro. É uma promessa para quem pretende escrever o valor no buffer. É um futuro para quem espera que esse valor seja colocado no buffer.

Especificamente, para a API simultânea Scala, se você der uma olhada no traço Promise aqui, poderá ver como os métodos do objeto complementar Promise são implementados:

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

}

Agora, essas implementações de promessas, DefaultPromise e KeptPromise podem ser encontradas aqui . Ambos estendem um pequeno traço básico que por acaso tem o mesmo nome, mas está localizado em um pacote diferente:

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

Então você pode ver o que eles querem dizer p.future == p.

DefaultPromiseé o buffer que estava me referindo acima, enquanto KeptPromiseé um buffer com o valor colocado desde sua criação.

Em relação ao seu exemplo, o bloco futuro que você usa ali cria uma promessa nos bastidores. Vamos olhar a definição de futureem aqui :

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

Seguindo a cadeia de métodos, você acaba no 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
  }
}

Então, como você pode ver, o resultado que você obtém do seu bloco produtor é transformado em uma promessa.

EDIÇÃO DEPOIS :

Com relação ao uso no mundo real: na maioria das vezes, você não lida diretamente com as promessas. Se você usar uma biblioteca que realiza computação assíncrona, trabalhará apenas com o futuro retornado pelos métodos da biblioteca. As promessas são, neste caso, criadas pela biblioteca - você está apenas trabalhando com a leitura do que esses métodos fazem.

Mas se você precisar implementar sua própria API assíncrona, terá que começar a trabalhar com eles. Suponha que você precise implementar um cliente HTTP assíncrono em cima de, digamos, Netty. Então, seu código será parecido com este

    def makeHTTPCall(request: Request): Future[Response] = {
        val p = Promise[Response]
        registerOnCompleteCallback(buffer => {
            val response = makeResponse(buffer)
            p success response
        })
        p.future
    }
Marius danila
fonte
3
@xiefei O caso de uso para Promises deve estar no código de implementação. Futureé uma coisa boa, somente leitura, que você pode expor ao código do cliente. Além disso, a Future.future{...}sintaxe às vezes pode ser complicada.
Dylan
11
Você pode ver assim: você não pode ter um futuro sem uma promessa. Um futuro não pode retornar um valor se não houver nenhuma promessa que seja cumprida em primeiro lugar. As promessas não são opcionais, são o lado escrito obrigatório de um futuro. Você não pode trabalhar apenas com futuros porque não haveria ninguém para lhes fornecer o valor de retorno.
Marius Danila
4
Acho que entendi o que você quer dizer com usos no mundo real: atualizei minha resposta para dar um exemplo.
Marius Danila
2
@Marius: Considerando o exemplo do mundo real dado, e se makeHTTPCall fosse implementado assim: def makeHTTPCall(request: Request): Future[Response] = { Future { registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) response }) } }
puneetk
1
@puneetk então você terá o futuro, que se completa logo após registerOnCompleteCallback()terminar. Além disso, ele não retorna Future[Response]. Em Future[registerOnCompleteCallback() return type]vez disso, ele retorna .
Evgeny Veretennikov