An Akka/Scala promise that requires two actors to complete

一个人想着一个人 提交于 2019-12-06 13:20:34

You are right not to want to use ask -- that should be avoided when possible.

Here is how you can send Promises to buyer and seller actors, then compose the corresponding Futures and arrange to process the transaction when the Promises are fulfilled.

import scala.concurrent.{future, promise}
import scala.concurrent.ExecutionContext.Implicits.global

val  fundsPromise = promise[Funds]
val sharesPromise = promise[Shares]

 buyerActor ! GetFunds(amount, fundsPromise)
sellerActor ! GetShares(numShares, stock, sharesPromise)

val futureFunds =   fundsPromise.future
val futureShares = sharesPromise.future

val purchase =
  for { funds <- futureFunds; shares <- futureShares }
    yield transact(funds, shares)

purchase onComplete {
  case Success(transactionResult) =>
     buyerActor ! PutShares(numShares)
    sellerActor ! PutFunds(amount)
    // tell somebody it worked
  case Failure(t) =>
    futureFunds  onSuccess { case _ =>  buyerActor ! PutFunds(amount) }
    futureShares onSuccess { case _ => sellerActor ! PutShares(numShares) }
    // tell somebody it failed because of t
}

Note that the ultimate result can be failure for several reasons: because you could not get the funds from the buyer, because you could not get the shares from the seller, or because something went wrong in transact. So on failure we check the funds and shares futures to see whether we got them, and if so we give back what we took.

Also note that we close over amount and numShares in futures, which means that you want them to be vals. If they were vars, you could end up using the wrong values when the futures actually run.

As bsmk points out, this assumes that buyerActor and sellerActor are in the same JVM as the above code. If not, you could have a local actor handle the promise upon hearing from the remote actor; if there is a better way, please let me know in the comments.

You could do something like the following:

def querySellingActor(seller : ActorRef) : Future[Boolean] = ???
def queryBuyingActor(buyer : ActorRef ) : Future[Boolean] = ???

// Calling these outside the for-comprehension 
// ensures that they are really parallel
val f1 = querySellingActor(sellerActor)
val f2 = queryBuyingActor(buyerActor)

val niceResult = for{
   sellerCanSell <- f1
   buyerCanBuy <- f2
} yield { sellerCanSell && buyerCanBuy }

niceResult is now a Future[Boolean] that can be passed around and you can install your call-backs on it.

I would use the short lived actor pattern, you should use the ask pattern only if you really need to:

case object TransactionTimeout
case class NewTransaction(buyer: ActorRef, seller: ActorRef)

class TransactionHandlerActor extends Actor with ActorLogging {

  var _scheduled: Cancellable = null
  var resultReceiver = Actor.noSender
  var hasBuyerAnswered = false
  var hasSellerAnswered = false

  def receive: Receive = {
    case NewTransaction(buyer, seller) =>
      resultReceiver = sender()
      buyer ! BuyRequest(...)
      seller ! SellRequest(...)
      _scheduled = context.system.scheduler.scheduleOnce(5 seconds)(self ! TransactionTimeout)

    case TransactionTimeout =>
      resultReceiver ! TransactionError(...)
      context.stop(self)

    case BuyerResponse(...) =>
      hasBuyerAnswered = true
      checkIfComplete()
    case SellerResponse(...) =>
      hasSellerAnswered = true
      checkIfComplete()
  }

  def checkIfComplete() = {
    if(hasBuyerAnswered && hasSellerAnswered) {
      /* finalize operation */
      resultReceiver ! TransactionCompleted(...)
      _scheduled.cancel()
      context.stop(self)
    }
  }
}

Basically the temporary actor handles the transaction and stops himself when finished, the timeout is there to ensure that he won't wait forever for a response. This is a very simple example of what you can do, code is untested.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!