Apa kasus penggunaan scala.concurrent.Promise?

94

Saya membaca SIP-14 dan konsepnya Futuremasuk akal dan mudah dimengerti. Tapi punya dua pertanyaan tentang Promise:

  1. Kata SIP Depending on the implementation, it may be the case that p.future == p. Bagaimana ini bisa terjadi? Adalah Futuredan Promisetidak dua jenis yang berbeda?

  2. Kapan sebaiknya kita menggunakan Promise? producer and consumerKode contoh :

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

mudah dibaca tetapi apakah kita benar-benar perlu menulis seperti itu? Saya mencoba menerapkannya hanya dengan Future dan tanpa Promise seperti ini:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

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

Apa perbedaan antara ini dan contoh yang diberikan dan apa yang membuat Janji diperlukan?

xiefei
sumber
Dalam contoh pertama continueDoingSomethingUnrelated () mengevaluasi setelah productionSomething () di thread yang sama.
senia
1
Untuk menjawab pertanyaan # 1, ya Futuredan Promisemerupakan dua jenis yang berbeda, tetapi seperti yang Anda lihat dari github.com/scala/scala/blob/master/src/library/scala/concurrent/…Promise implementasi khusus ini juga meluas Future.
Dylan

Jawaban:

118

Janji dan Masa Depan adalah konsep yang saling melengkapi. Masa Depan adalah nilai yang akan diambil, yah, suatu saat nanti dan Anda dapat melakukan banyak hal dengannya saat peristiwa itu terjadi. Oleh karena itu, ini adalah titik akhir pembacaan atau keluar dari sebuah komputasi - itu adalah sesuatu yang Anda ambil nilainya.

A Promise, dengan analogi, adalah sisi penulisan dari perhitungan. Anda membuat promise yang merupakan tempat Anda akan meletakkan hasil penghitungan dan dari promise tersebut Anda mendapatkan masa depan yang akan digunakan untuk membaca hasil yang dimasukkan ke dalam promise. Saat Anda akan menyelesaikan sebuah Janji, baik karena gagal atau berhasil, Anda akan memicu semua perilaku yang terkait dengan Masa Depan terkait.

Mengenai pertanyaan pertama Anda, bagaimana mungkin itu untuk janji yang kita miliki p.future == p. Anda dapat membayangkan ini seperti buffer satu item - sebuah wadah yang awalnya kosong dan Anda dapat menyimpan satu nilai yang akan menjadi isinya selamanya. Sekarang, tergantung pada sudut pandang Anda, ini adalah Janji dan Masa Depan. Ini adalah janji untuk seseorang yang berniat untuk menulis nilai di buffer. Ini adalah masa depan bagi seseorang yang menunggu nilai itu dimasukkan ke dalam buffer.

Khususnya, untuk Scala concurrent API, jika Anda melihat sifat Promise di sini, Anda dapat melihat bagaimana metode dari objek pendamping Promise diimplementasikan:

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

}

Sekarang, implementasi dari promise, DefaultPromise dan KeptPromise dapat ditemukan di sini . Keduanya memperluas sifat kecil dasar yang kebetulan memiliki nama yang sama, tetapi terletak di paket yang berbeda:

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

Jadi Anda bisa melihat apa yang mereka maksud p.future == p.

DefaultPromiseadalah buffer yang saya maksud di atas, sedangkan KeptPromisebuffer dengan nilai yang dimasukkan dari pembuatannya.

Mengenai contoh Anda, blok masa depan yang Anda gunakan di sana sebenarnya menciptakan janji di balik layar. Mari kita lihat definisi futuredi sini :

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

Dengan mengikuti rantai metode Anda berakhir di 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
  }
}

Jadi, seperti yang Anda lihat, hasil yang Anda peroleh dari blok produser Anda dituangkan menjadi sebuah janji.

EDIT TERAKHIR :

Mengenai penggunaan dunia nyata: Seringkali Anda tidak akan berurusan dengan janji secara langsung. Jika Anda akan menggunakan pustaka yang melakukan komputasi asinkron, Anda hanya akan bekerja dengan masa depan yang dikembalikan oleh metode pustaka. Janji, dalam hal ini, dibuat oleh perpustakaan - Anda hanya bekerja dengan akhir membaca dari apa yang dilakukan metode tersebut.

Tetapi jika Anda perlu menerapkan API asinkron Anda sendiri, Anda harus mulai bekerja dengannya. Misalkan Anda perlu menerapkan klien HTTP asinkron di atas, katakanlah, Netty. Kemudian kode Anda akan terlihat seperti ini

    def makeHTTPCall(request: Request): Future[Response] = {
        val p = Promise[Response]
        registerOnCompleteCallback(buffer => {
            val response = makeResponse(buffer)
            p success response
        })
        p.future
    }
Marius Danila
sumber
3
@xiefei Kasus penggunaan untuk Promises harus dalam kode implementasi. Futureadalah hal yang bagus dan hanya-baca yang dapat Anda tampilkan ke kode klien. Selain itu, Future.future{...}sintaks terkadang bisa merepotkan.
Dylan
11
Anda bisa melihatnya seperti ini: Anda tidak bisa memiliki masa depan tanpa janji. Masa depan tidak dapat mengembalikan nilai jika tidak ada janji yang diselesaikan sejak awal. Janji bukanlah pilihan, itu adalah sisi penulisan wajib di masa depan. Anda tidak dapat bekerja hanya dengan masa depan karena tidak akan ada orang yang memberi mereka nilai pengembalian.
Marius Danila
4
Saya rasa saya mengerti apa yang Anda maksud dengan penggunaan dunia nyata: Saya telah memperbarui tanggapan saya untuk memberi Anda contoh.
Marius Danila
2
@Marius: Mempertimbangkan contoh dunia nyata yang diberikan, bagaimana jika makeHTTPCall diimplementasikan seperti ini: def makeHTTPCall(request: Request): Future[Response] = { Future { registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) response }) } }
puneetk
1
@puneetk maka Anda akan memiliki masa depan, yang selesai tepat setelah registerOnCompleteCallback()selesai. Juga, itu tidak kembali Future[Response]. Ia kembali Future[registerOnCompleteCallback() return type]sebagai gantinya.
Evgeny Veretennikov