我有一个API(在运行时)创建actorA。然后,A(也在运行时)创建Actor B。
我还有另一个API创建Actor C(与Actor A不同,它们之间没有命令代码),而C创建了ActorD。
我想在B和D完成处理它们的消息后就正常关闭A和C(A和C不一定一起运行,它们是无关的)。
将毒丸发送到A / C还不够好,因为孩子(B / D)仍然会停止上下文,并且无法完成任务。
我知道我需要实施一种新型的消息。 我不了解如何创建基础结构,因此A和C都将知道如何响应此消息而又没有相同的重复接收方法。
我发现的解决方案是创建一个扩展Actor并覆盖未处理方法的新特征。 代码如下:
object SuicideActor {
case class PleaseKillYourself()
case class IKilledMyself()
}
trait SuicideActor extends Actor {
override def unhandled(message: Any): Unit = message match {
case PleaseKillYourself =>
Logger.debug(s"Actor ${self.path} received PleaseKillYourself - stopping children and aborting...")
val livingChildren = context.children.size
if (livingChildren == 0) {
endLife()
} else {
context.children.foreach(_ ! PleaseKillYourself)
context become waitForChildren(livingChildren)
}
case _ => super.unhandled(message)
}
protected[crystalball] def waitForChildren(livingChildren: Int): Receive = {
case IKilledMyself =>
val remaining = livingChildren - 1
if (remaining == 0) { endLife() }
else { context become waitForChildren(remaining) }
}
private def endLife(): Unit = {
context.parent ! IKilledMyself
context stop self
}
}
但是,这听起来有点头。...是否有更好的(非non头)解决方案?
答案 0 :(得分:2)
我认为没有必要设计自己的终止程序,并且可能会导致将来的代码维护人员头痛,因为这是非标准的akka行为,需要再次理解。
如果A和C不相关并且可以独立终止,则可以使用以下选项。为避免混淆,在解释中我只使用演员A和B。
选项1。
演员A在新创建的演员B上使用context.watch
,并以其Terminated
方法对receive
消息做出反应。角色B完成其任务后将调用context.stop(context.self)
,这将生成Terminated
事件,该事件将由角色A处理,该事件可以在需要时清除其状态并终止。
检查这些docs以获得更多详细信息。
选项2。
演员B完成自己的任务后,会调用context.stop(context.parent)
直接终止其父。如果需要,此选项不允许家长做出反应并执行其他清理任务。
最后,在角色A和C之间共享此逻辑可以使用您的特质来完成,但是逻辑很小,并且重复代码并不是一件坏事。
答案 1 :(得分:0)
所以花了我一些时间,但我找到了答案。 我实施了The Reaper Pattern
SuicideActor在完成阻止后会创建一个专用的Reaper actor。收割者watch
的所有SuicideActor孩子,一旦他们全部终止,就会向SuicideActor及其本身发送PoisonPill
SuicideActor代码是:
trait SuicideActor extends Actor {
def killSwitch(block: => Unit): Unit = {
block
Logger.info(s"Actor ${self.path.name} is commencing suicide sequence...")
context become PartialFunction.empty
val children = context.children
val reaper = context.system.actorOf(ReaperActor.props(self), s"ReaperFor${self.path.name}")
reaper ! Reap(children.toSeq)
}
override def postStop(): Unit = Logger.debug(s"Actor ${self.path.name} is dead.")
}
收割者是: 对象ReaperActor {
case class Reap(underWatch: Seq[ActorRef])
def props(supervisor: ActorRef): Props = {
Props(new ReaperActor(supervisor))
}
}
class ReaperActor(supervisor: ActorRef) extends Actor {
override def preStart(): Unit = Logger.info(s"Reaper for ${supervisor.path.name} started")
override def postStop(): Unit = Logger.info(s"Reaper for ${supervisor.path.name} ended")
override def receive: Receive = {
case Reap(underWatch) =>
if (underWatch.isEmpty) {
killLeftOvers
} else {
underWatch.foreach(context.watch)
context become reapRemaining(underWatch.size)
underWatch.foreach(_ ! PoisonPill)
}
}
def reapRemaining(livingActorsNumber: Int): Receive = {
case Terminated(_) =>
val remainingActorsNumber = livingActorsNumber - 1
if (remainingActorsNumber == 0) {
killLeftOvers
} else {
context become reapRemaining(remainingActorsNumber)
}
}
private def killLeftOvers = {
Logger.debug(s"All children of ${supervisor.path.name} are dead killing supervisor")
supervisor ! PoisonPill
self ! PoisonPill
}
}