虽然纠正了我的SRP方式错误yesterday,但我仍然想知道如何干净地保证对akka中异步资源的单线程访问,例如文件句柄。显然,我不希望允许从不同的线程调度多个读写操作,但如果我的actor在该文件上调用基于future的API,那就是可能发生的事情。
我提出的最佳模式是:
trait AsyncIO {
def Read(offset: Int, count: Int) : Future[ByteBuffer] = ???
}
object GuardedIOActor {
case class Read(offset: Int, count: Int)
case class ReadResult(data: ByteBuffer)
private case class ReadCompleted()
}
class GuardedIOActor extends Actor with Stash with AsyncIO {
import GuardedIOActor._
var caller :Option[ActorRef] = None
def receive = {
case Read(offset,count) => caller match {
case None => {
caller = Some(sender)
Read(offset,count).onSuccess({
case data => {
self ! ReadCompleted()
caller.get ! ReadResult(data)
}
})
}
case Some(_) => {
stash()
}
}
case ReadCompleted() => {
caller = None
unstashAll()
}
}
}
但是这个要求对我来说不够神秘,无法推出那种kludge。我的意思是应该有大量资源需要同步访问,但有一个异步API。我是否会忽略一些常见的命名模式?
答案 0 :(得分:6)
我认为你的解决方案的要点并不是那么糟糕,但你可以使用context.become
使你的演员更像是一个状态机:
class GaurdedIOActor extends Actor with Stash with AsyncIO {
import GuardedIOActor._
def receive = notReading
def notReading: Receive = {
case Read(offset, count) => {
val caller = sender
Read(offset,count).onSuccess({
case data => {
self ! ReadCompleted()
caller ! ReadResult(data)
}
})
context.become(reading)
}
}
def reading: Receive = {
case r: Read => stash()
case ReadCompleted() => {
context.become(notReading)
unstashAll()
}
}
}
现在你的演员有两个明确定义的状态,而且不需要var
答案 1 :(得分:1)
我意识到这个加法已经晚了一年,但是这个问题帮助我解释了我遇到的类似情况。以下特征封装了上面建议的功能,以减少actor中的样板。它可以与Stash的任何演员混在一起。用法类似于pipeTo
模式;只需键入future.pipeSequentiallyTo(sender)
,您的演员就不会处理消息,直到future
完成并且已发送回复。
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.language.implicitConversions
import scala.util.Failure
import scala.util.Success
import akka.actor.Actor
import akka.actor.ActorRef
import akka.actor.Stash
import akka.actor.Status
trait SequentialPipeToSupport { this: Actor with Stash =>
case object ProcessingFinished
def gotoProcessingState() = context.become(processingState)
def markProcessingFinished() = self ! ProcessingFinished
def processingState: Receive = {
case ProcessingFinished =>
unstashAll()
context.unbecome()
case _ =>
stash()
}
final class SequentialPipeableFuture[T](val future: Future[T])(implicit executionContext: ExecutionContext) {
def pipeSequentiallyTo(recipient: ActorRef): Future[T] = {
gotoProcessingState()
future onComplete {
case Success(r) =>
markProcessingFinished()
recipient ! r
case Failure(f) =>
markProcessingFinished()
recipient ! Status.Failure(f)
}
future
}
}
implicit def pipeSequentially[T](future: Future[T])(implicit executionContext: ExecutionContext) =
new SequentialPipeableFuture(future)
}
也可以Gist
的形式提供