Каковы варианты использования scala.concurrent.Promise?

93

Я читаю SIP-14, и концепция Futureимеет смысл и проста для понимания. Но есть два вопроса Promise:

  1. SIP говорит Depending on the implementation, it may be the case that p.future == p. Как это может быть? Есть два разных типа, Futureа Promiseне два?

  2. Когда мы должны использовать Promise? Пример 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()
        }
    }
    

легко читать, но действительно ли нам нужно так писать? Я пытался реализовать это только с Future и без Promise вот так:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

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

В чем разница между этим и данным примером и для чего требуется Promise?

xiefei
источник
В первом примере continueDoingSomethingUnrelated () выполняет оценку после produSomething () в том же потоке.
senia
1
Чтобы ответить на вопрос № 1, да, Futureи Promiseэто два разных типа, но, как вы можете видеть из github.com/scala/scala/blob/master/src/library/scala/concurrent/ ... эта конкретная Promiseреализация также расширяется Future.
Дилан

Ответы:

118

Обещание и будущее - это взаимодополняющие концепции. Будущее - это значение, которое будет извлечено когда-нибудь в будущем, и вы можете что-нибудь делать с ним, когда это событие произойдет. Следовательно, это конечная точка чтения или вывода вычисления - это то, из чего вы получаете значение.

По аналогии, обещание - это записывающая сторона вычислений. Вы создаете обещание, которое является местом, куда вы помещаете результат вычисления, и из этого обещания вы получаете будущее, которое будет использоваться для чтения результата, помещенного в обещание. Когда вы выполните обещание, либо неудачно, либо успешно, вы активируете все поведение, которое было привязано к соответствующему будущему.

Что касается вашего первого вопроса, как может быть, что для обещания p у нас есть p.future == p . Вы можете представить это как буфер с одним элементом - контейнер, который изначально пуст, а после него вы можете сохранить одно значение, которое навсегда станет его содержимым. Теперь, в зависимости от вашей точки зрения, это и обещание, и будущее. Это обещание для тех, кто намеревается записать значение в буфер. Это будущее для тех, кто ждет, пока это значение будет помещено в буфер.

В частности, для параллельного API Scala, если вы посмотрите на черте Promise в здесь вы можете увидеть , как реализуются методы от объекта - компаньона 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))

}

Теперь эти реализации обещаний, DefaultPromise и KeptPromise можно найти здесь . Оба они расширяют базовую маленькую черту, которая имеет одно и то же имя, но находится в другом пакете:

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

Итак, вы можете понять, что они имеют в виду p.future == p.

DefaultPromise- это буфер, о котором я говорил выше, а KeptPromiseэто буфер со значением, введенным с момента его создания.

Что касается вашего примера, будущий блок, который вы там используете, фактически создает обещание за кулисами. Давайте посмотрим на определение futureв здесь :

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

Следуя цепочке методов, вы попадаете в 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
  }
}

Итак, как видите, результат, который вы получаете от вашего блока производителя, переливается в обещание.

ПОЗЖЕ РЕДАКТИРОВАТЬ :

Что касается реального использования: в большинстве случаев вы не будете иметь дело с обещаниями напрямую. Если вы будете использовать библиотеку, которая выполняет асинхронные вычисления, тогда вы просто будете работать с фьючерсами, возвращаемыми методами библиотеки. Обещания в этом случае создаются библиотекой - вы просто работаете с концом чтения того, что делают эти методы.

Но если вам нужно реализовать собственный асинхронный API, вам придется начать с ними работать. Предположим, вам нужно реализовать асинхронный HTTP-клиент поверх, скажем, Netty. Тогда ваш код будет выглядеть примерно так

    def makeHTTPCall(request: Request): Future[Response] = {
        val p = Promise[Response]
        registerOnCompleteCallback(buffer => {
            val response = makeResponse(buffer)
            p success response
        })
        p.future
    }
Мариус Данила
источник
3
@xiefei Вариант использования Promises должен быть в коде реализации. Future- это приятная вещь, доступная только для чтения, которую вы можете предоставить клиентскому коду. Кроме того, Future.future{...}иногда синтаксис может быть громоздким.
Дилан
11
Вы можете видеть это так: у вас не может быть будущего без обещаний. Future не может вернуть значение, если нет обещания, которое было выполнено в первую очередь. Обещания не являются необязательными, это обязательная письменная сторона будущего. Вы не можете работать только с фьючерсами, потому что некому было бы предоставить им возвращаемое значение.
Мариус Данила
4
Думаю, я понимаю, что вы подразумеваете под использованием в реальном мире: я обновил свой ответ, чтобы привести вам пример.
Мариус Данила
2
@Marius: Учитывая приведенный пример из реальной жизни, что, если makeHTTPCall реализован следующим образом: def makeHTTPCall(request: Request): Future[Response] = { Future { registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) response }) } }
puneetk
1
@puneetk, тогда у вас будет будущее, которое завершится сразу после registerOnCompleteCallback()завершения. Кроме того, он не возвращается Future[Response]. Future[registerOnCompleteCallback() return type]Вместо этого он возвращается .
Евгений Веретенников