Які випадки використання 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()
        }
    }

читається легко, але чи справді нам потрібно писати так? Я намагався реалізувати це лише з майбутнім і без обіцянок, як це:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

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

У чому різниця між цим і наведеним прикладом і що робить обіцянку необхідною?


У першому прикладі continueDoingSomethingUnrelated () обчислює після produceSomething () у тому ж потоці.
senia

1
Для відповіді на питання # 1, та Futureі Promiseдва окремих типу, але , як ви можете бачити з github.com/scala/scala/blob/master/src/library/scala/concurrent / ... етщій Promiseреалізація поширюється Futureтакож.
Ділан,

Відповіді:


118

Обіцянка та майбутнє - це взаємодоповнюючі поняття. Майбутнє - це значення, яке буде отримано, колись у майбутньому, і ви можете робити з ним речі, коли ця подія станеться. Отже, це кінцева точка обчислення чи зчитування - це те, з чого ви отримуєте значення.

Обіцянка - це, за аналогією, письмова сторона обчислення. Ви створюєте обіцянку, в якій ви будете розміщувати результат обчислення, і з цієї обіцянки ви отримаєте майбутнє, яке буде використано для зчитування результату, який було вкладено в обіцянку. Коли ви виконаєте обіцянку, або через невдачу, або через успіх, ви ініціюєте всю поведінку, яка була прив’язана до пов'язаного майбутнього.

Щодо вашого першого питання, то як може бути, що для обіцянки р ми маємо 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
Ви можете бачити це так: ви не можете мати майбутнє без обіцянки. Майбутнє не може повернути значення, якщо спочатку немає жодної обіцянки. Обіцянки не є необов’язковими, вони є обов’язковою стороною майбутнього. Ви не можете працювати лише з ф’ючерсами, тому що не було б когось, хто б надав їм повернену вартість.
Маріус Даніла,

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]Натомість він повертається .
Євген Веретенников
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.