私は SIP-14 を読んでおり、Future
の概念は完璧な意味を持ち、理解しやすいです。ただし、Promise
について2つの質問があります。
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が必要な理由は何ですか?
約束と未来は補完的な概念です。 Futureは、将来、いつか取得される値であり、そのイベントが発生したときに何かを行うことができます。したがって、それは計算の読み取りまたは出力エンドポイントです-値を取得するものです。
約束は、類推により、計算の書き込み側です。計算の結果を置く場所であるプロミスを作成し、そのプロミスから、プロミスに入れられた結果を読み取るために使用される未来を取得します。失敗または成功によってPromiseを完了すると、関連するFutureに関連付けられたすべての動作がトリガーされます。
最初の質問について、約束pに対してp.future == p
。これは、単一アイテムのバッファのように想像できます。最初は空のコンテナであり、後からコンテンツを永久に保存する1つの値を保存できます。今、あなたの視点に応じて、これは約束と未来の両方です。バッファに値を書き込もうとする人にとっては約束です。その値がバッファに格納されるのを待つ人にとっては未来です。
具体的には、ScalaコンカレントAPIの場合、 here の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))
}
現在、Promise、DefaultPromise、KeptPromiseの実装は here にあります。どちらも同じ名前を持つベースの小さな特性を拡張しますが、それは別のパッケージにあります:
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
はその作成から値が入れられたバッファーです。
あなたの例に関して、そこで使用する将来のブロックは、実際に裏で約束を作成します。 here の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を実装する必要がある場合は、それらの操作を開始する必要があります。たとえば、Nettyの上に非同期HTTPクライアントを実装する必要があるとします。次に、コードは次のようになります
def makeHTTPCall(request: Request): Future[Response] = {
val p = Promise[Response]
registerOnCompleteCallback(buffer => {
val response = makeResponse(buffer)
p success response
})
p.future
}