Akka:守护异步资源

时间:2014-05-14 17:08:03

标签: scala asynchronous io akka future

虽然纠正了我的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。我是否会忽略一些常见的命名模式?

2 个答案:

答案 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

的形式提供