akka-typed的actor從建立、啟用、狀態轉換、停用、監視等生命週期管理方式和akka-classic還是有一定的不同之處。這篇我們就介紹一下akka-typed的actor生命週期管理。
每一種actor都是通過定義它的行為屬性behavior形成模版,然後由對上一層的父輩actor用spawn方法產生actor例項的。產生的actor例項加入一個系統的由上至下樹形結構,直接在spawn產生自己的父輩之下。akka-typed的守護guardian-actor,即根部root-actor是通過在定義ActorSystem時指定併產生的。如下:
val config = ConfigFactory.load("application.conf")
val man: ActorSystem[GreetStarter.Command] = ActorSystem(GreetStarter(), "greetDemo",config)
man ! GreetStarter.RepeatedGreeting("Tiger",1.seconds)
在某種意義上,這個ActorSystem例項man就代表root-actor。我們可以向man傳送訊息然後由GreetStarter的behavior用自己的ActorContext進行spawn,stop,watch及分派計算任務等,其實就是一個程式的集線器:
object GreetStarter {
import Messages._
def apply(): Behavior[SayHi] = {
Behaviors.setup { ctx =>
val props = DispatcherSelector.fromConfig("akka.actor.default-blocking-io-dispatcher")
val helloActor = ctx.spawn(HelloActor(), "hello-actor",props)
val greeter = ctx.spawn(Greeter(helloActor), "greeter")
ctx.watch(greeter)
ctx.watchWith(helloActor,StopWorker("something happend"))
Behaviors.receiveMessage { who =>
if (who.name == "stop") {
ctx.stop(helloActor)
ctx.stop(greeter)
Behaviors.stopped
} else {
greeter ! who
Behaviors.same
}
}
}
}
}
但是,總有時候我們需要在root-actor的ActorContext之外來進行一些製造、使用actor的操作。下面這個官方文件上的例子是很好的示範:
import akka.actor.typed.Behavior
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.LoggerOps
object HelloWorldMain {
def apply(): Behavior[SpawnProtocol.Command] =
Behaviors.setup { context =>
// Start initial tasks
// context.spawn(...)
SpawnProtocol()
}
}
object Main extends App {
implicit val system: ActorSystem[SpawnProtocol.Command] =
ActorSystem(HelloWorldMain(), "hello")
// needed in implicit scope for ask (?)
import akka.actor.typed.scaladsl.AskPattern._
implicit val ec: ExecutionContext = system.executionContext
implicit val timeout: Timeout = Timeout(3.seconds)
val greeter: Future[ActorRef[HelloWorld.Greet]] =
system.ask(SpawnProtocol.Spawn(behavior = HelloWorld(), name = "greeter", props = Props.empty, _))
val greetedBehavior = Behaviors.receive[HelloWorld.Greeted] { (context, message) =>
context.log.info2("Greeting for {} from {}", message.whom, message.from)
Behaviors.stopped
}
val greetedReplyTo: Future[ActorRef[HelloWorld.Greeted]] =
system.ask(SpawnProtocol.Spawn(greetedBehavior, name = "", props = Props.empty, _))
for (greeterRef <- greeter; replyToRef <- greetedReplyTo) {
greeterRef ! HelloWorld.Greet("Akka", replyToRef)
}
...
}
可以看到所有操作都在actor框架之外進行的。這個SpawnProtocol本身就是一個actor,如下:
object SpawnProtocol {
...
final case class Spawn[T](behavior: Behavior[T], name: String, props: Props, replyTo: ActorRef[ActorRef[T]])
extends Command
...
def apply(): Behavior[Command] =
Behaviors.receive { (ctx, msg) =>
msg match {
case Spawn(bhvr, name, props, replyTo) =>
val ref =
if (name == null || name.equals(""))
ctx.spawnAnonymous(bhvr, props)
else {
@tailrec def spawnWithUniqueName(c: Int): ActorRef[Any] = {
val nameSuggestion = if (c == 0) name else s"$name-$c"
ctx.child(nameSuggestion) match {
case Some(_) => spawnWithUniqueName(c + 1) // already taken, try next
case None => ctx.spawn(bhvr, nameSuggestion, props)
}
}
spawnWithUniqueName(0)
}
replyTo ! ref
Behaviors.same
}
}
}
外界通過傳送Spawn訊息來指定產生新的actor。
actor的狀態切換就是從一種behavior轉到另一種behavior。我們可以自定義behavior或者用現成的Behaviors.???。如果只是涉及內部變數變化,那麼可以直接生成帶著變數的當前behavior,如下:
object HelloWorldBot {
def apply(max: Int): Behavior[HelloWorld.Greeted] = {
bot(0, max)
}
private def bot(greetingCounter: Int, max: Int): Behavior[HelloWorld.Greeted] =
Behaviors.receive { (context, message) =>
val n = greetingCounter + 1
context.log.info2("Greeting {} for {}", n, message.whom)
if (n == max) {
Behaviors.stopped
} else {
message.from ! HelloWorld.Greet(message.whom, context.self)
bot(n, max)
}
}
}
actor停用可以由直屬父輩actor的ActorContext.stop或者自身的Behaviors.stopped來實現。Behaviors.stopped可以帶入一個清理函式。在actor完全停止之前進行一些清理操作:
object MasterControlProgram {
sealed trait Command
final case class SpawnJob(name: String) extends Command
case object GracefulShutdown extends Command
// Predefined cleanup operation
def cleanup(log: Logger): Unit = log.info("Cleaning up!")
def apply(): Behavior[Command] = {
Behaviors
.receive[Command] { (context, message) =>
message match {
case SpawnJob(jobName) =>
context.log.info("Spawning job {}!", jobName)
context.spawn(Job(jobName), name = jobName)
Behaviors.same
case GracefulShutdown =>
context.log.info("Initiating graceful shutdown...")
// perform graceful stop, executing cleanup before final system termination
// behavior executing cleanup is passed as a parameter to Actor.stopped
Behaviors.stopped { () =>
cleanup(context.system.log)
}
}
}
.receiveSignal {
case (context, PostStop) =>
context.log.info("Master Control Program stopped")
Behaviors.same
}
}
}
實際上一個actor轉入停用stop狀態可以在另一個作為監視actor的receiveSignal獲取,如下:
object GreetStarter {
import Messages._
def apply(): Behavior[SayHi] = {
Behaviors.setup { ctx =>
val props = DispatcherSelector.fromConfig("akka.actor.default-blocking-io-dispatcher")
val helloActor = ctx.spawn(HelloActor(), "hello-actor",props)
val greeter = ctx.spawn(Greeter(helloActor), "greeter")
ctx.watch(greeter)
ctx.watchWith(helloActor,StopWorker("something happend"))
Behaviors.receiveMessage { who =>
if (who.name == "stop") {
ctx.stop(helloActor)
ctx.stop(greeter)
Behaviors.stopped
} else {
greeter ! who
Behaviors.same
}
}.receiveSignal {
case (context, Terminated(ref)) =>
context.log.info("{} stopped!", ref.path.name)
Behaviors.same
}
}
}
}
下面是.receiveSignal函式及其捕獲的Signal訊息:
trait Receive[T] extends Behavior[T] {
def receiveSignal(onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]]): Behavior[T]
}
trait Signal
/**
* Lifecycle signal that is fired upon restart of the Actor before replacing
* the behavior with the fresh one (i.e. this signal is received within the
* behavior that failed).
*/
sealed abstract class PreRestart extends Signal
case object PreRestart extends PreRestart {
def instance: PreRestart = this
}
/**
* Lifecycle signal that is fired after this actor and all its child actors
* (transitively) have terminated. The [[Terminated]] signal is only sent to
* registered watchers after this signal has been processed.
*/
sealed abstract class PostStop extends Signal
// comment copied onto object for better hints in IDEs
/**
* Lifecycle signal that is fired after this actor and all its child actors
* (transitively) have terminated. The [[Terminated]] signal is only sent to
* registered watchers after this signal has been processed.
*/
case object PostStop extends PostStop {
def instance: PostStop = this
}
object Terminated {
def apply(ref: ActorRef[Nothing]): Terminated = new Terminated(ref)
def unapply(t: Terminated): Option[ActorRef[Nothing]] = Some(t.ref)
}