Các trường hợp sử dụng của scala.concurrent.Promise là gì?


93

Tôi đang đọc SIP-14 và khái niệm về nó Futurecó ý nghĩa hoàn hảo và dễ hiểu. Nhưng có hai câu hỏi về Promise:

  1. SIP nói Depending on the implementation, it may be the case that p.future == p. Làm sao có thể? Có FuturePromisekhông phải là hai loại khác nhau?

  2. Khi nào chúng ta nên sử dụng a Promise? producer and consumerMã ví dụ :

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

rất dễ đọc nhưng chúng ta có thực sự cần viết như vậy không? Tôi đã cố gắng triển khai nó chỉ với Future và không có Promise như thế này:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

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

Sự khác biệt giữa ví dụ này và ví dụ đã cho là gì và điều gì khiến một Lời hứa trở nên cần thiết?


Trong ví dụ đầu tiên, continueDoingSomethingUnosystem () đánh giá sau productionSomething () trong cùng một luồng.
senia

1
Để trả lời câu hỏi số 1, có FuturePromiselà hai loại riêng biệt, nhưng như bạn có thể thấy từ github.com/scala/scala/blob/master/src/library/scala/concurrent/…Promise triển khai cụ thể này Futurecũng mở rộng .
Dylan

Câu trả lời:


118

Lời hứa và tương lai là những khái niệm bổ sung cho nhau. Tương lai là một giá trị sẽ được truy xuất, tốt, vào một thời điểm nào đó trong tương lai và bạn có thể làm những việc với nó khi sự kiện đó xảy ra. Do đó, nó là điểm cuối đọc hoặc ra của một phép tính - nó là thứ mà bạn lấy ra một giá trị từ đó.

Một lời hứa, bằng cách tương tự, là mặt viết của phép tính. Bạn tạo một lời hứa là nơi bạn đặt kết quả tính toán và từ lời hứa đó, bạn sẽ có được một tương lai sẽ được sử dụng để đọc kết quả được đưa vào lời hứa. Khi bạn hoàn thành một Lời hứa, dù thất bại hay thành công, bạn sẽ kích hoạt tất cả các hành vi gắn liền với Tương lai liên quan.

Đối với câu hỏi đầu tiên của bạn, làm thế nào nó có thể được cho một lời hứa p mà chúng tôi có p.future == p. Bạn có thể tưởng tượng điều này giống như một bộ đệm một mục - một thùng chứa ban đầu trống và sau đó bạn có thể lưu trữ một giá trị sẽ trở thành nội dung của nó mãi mãi. Bây giờ, tùy thuộc vào quan điểm của bạn, đây vừa là Lời hứa vừa là Tương lai. Đó là lời hứa cho những người có ý định ghi giá trị vào bộ đệm. Đó là một tương lai cho một người nào đó chờ đợi giá trị đó được đưa vào bộ đệm.

Cụ thể, đối với API đồng thời Scala, nếu bạn xem xét đặc điểm Promise ở đây, bạn có thể thấy cách các phương thức từ đối tượng đồng hành Promise được triển khai:

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

}

Bây giờ, bạn có thể tìm thấy những triển khai của các hứa hẹn, DefaultPromise và KeptPromise tại đây . Cả hai đều mở rộng một đặc điểm nhỏ cơ bản có cùng tên, nhưng nó nằm trong một gói khác:

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

Vì vậy, bạn có thể thấy ý nghĩa của chúng p.future == p.

DefaultPromiselà bộ đệm mà tôi đã đề cập ở trên, trong khi KeptPromiselà bộ đệm với giá trị được đưa vào từ chính quá trình tạo của nó.

Về ví dụ của bạn, khối tương lai mà bạn sử dụng ở đó thực sự tạo ra một lời hứa đằng sau hậu trường. Hãy xem định nghĩa của futurein here :

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

Bằng cách làm theo chuỗi phương pháp mà bạn kết thúc trong cấy ghép .

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

Vì vậy, như bạn có thể thấy, kết quả bạn nhận được từ khối nhà sản xuất của mình được đổ vào một lời hứa.

CHỈNH SỬA SAU :

Về việc sử dụng trong thế giới thực: Hầu hết thời gian bạn sẽ không giải quyết những lời hứa trực tiếp. Nếu bạn sẽ sử dụng một thư viện thực hiện tính toán không đồng bộ thì bạn sẽ chỉ làm việc với các tương lai được trả về bởi các phương thức của thư viện. Trong trường hợp này, lời hứa là do thư viện tạo ra - bạn chỉ đang làm việc với phần cuối đọc của những gì các phương pháp đó làm.

Nhưng nếu bạn cần triển khai API không đồng bộ của riêng mình, bạn sẽ phải bắt đầu làm việc với chúng. Giả sử bạn cần triển khai một ứng dụng khách HTTP không đồng bộ trên đầu trang, chẳng hạn như Netty. Sau đó, mã của bạn sẽ giống như thế này

    def makeHTTPCall(request: Request): Future[Response] = {
        val p = Promise[Response]
        registerOnCompleteCallback(buffer => {
            val response = makeResponse(buffer)
            p success response
        })
        p.future
    }

3
@xiefei Trường hợp sử dụng cho Promises phải nằm trong mã triển khai. Futurelà một thứ hay, chỉ đọc mà bạn có thể hiển thị với mã khách hàng. Ngoài ra, Future.future{...}đôi khi cú pháp có thể rườm rà.
Dylan

11
Bạn có thể thấy nó như thế này: bạn không thể có tương lai nếu không có một lời hứa. Tương lai không thể trả lại giá trị nếu không có lời hứa nào được hoàn thành ngay từ đầu. Những lời hứa không phải là tùy chọn, chúng là mặt bắt buộc của một tương lai. Bạn không thể chỉ làm việc với hợp đồng tương lai bởi vì sẽ không có ai cung cấp giá trị hoàn lại cho họ.
Marius Danila

4
Tôi nghĩ tôi hiểu ý bạn khi sử dụng trong thế giới thực: Tôi đã cập nhật câu trả lời của mình để cung cấp cho bạn một ví dụ.
Marius Danila

2
@Marius: Xét ví dụ thực tế nhất định, những gì nếu makeHTTPCall được thực hiện như thế này: def makeHTTPCall(request: Request): Future[Response] = { Future { registerOnCompleteCallback(buffer => { val response = makeResponse(buffer) response }) } }
puneetk

1
@puneetk thì bạn sẽ có tương lai, sẽ hoàn thành ngay sau khi registerOnCompleteCallback()hoàn thành. Ngoài ra, nó không trở lại Future[Response]. Nó trả về Future[registerOnCompleteCallback() return type]thay thế.
Evgeny Veretennikov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.