【问题标题】:Why is my sender() DeadLetters from a scheduled message?为什么我的 sender() DeadLetters 来自预定消息?
【发布时间】:2014-07-27 15:02:51
【问题描述】:

我正在尝试安排在一段时间后发送一条消息 - 一个简单的重试。

override def receive = {
  case Worked => ???
  case DidNotWork => 
    val target = sender() // Avoid closing over sender().
    import context._
    context.system.scheduler.scheduleOnce(500.milliseconds, target, TryAgain)
}

这按预期工作,但是,当我收到TryAgain 消息并访问sender() 以尝试将ActorRef 获取到此对象时,我得到DeadLetters。为什么会这样?

(请注意,问题在于另一个参与者中的 sender() 调用,它不在闭包中 - 这不是我关闭 sender() 的问题):

override def receive = {
  case TryOnce => sender() ! DidNotWork
  case TryAgain => sender() ! Worked // sender() here is DeadLetters!
}

完整示例(回复cmbaxter's comment):

import akka.actor.{Props, ActorSystem, Actor}
import scala.concurrent.duration._

object Main {
  def main(args: Array[String]) {
    val sys = ActorSystem("Test")
    val test = sys.actorOf(Props[Test], "test-actor")
    test ! "badtest"
    test ! "goodtest"
  }
}

class Test extends Actor {
  override def receive = {
    case "badtest" =>
      import context._
      context.system.scheduler.scheduleOnce(10.milliseconds, this.self, "bad")
    case "goodtest" =>
      import context.dispatcher
      context.system.scheduler.scheduleOnce(10.milliseconds, this.self, "good")
    case other => println(s"$sender $other")
  }
}

产生:

Actor[akka://Test/deadLetters] bad
Actor[akka://Test/user/test-actor#621986067] good

【问题讨论】:

    标签: scala akka


    【解决方案1】:

    这是由于import context._ 调用而发生的。此行在解析 scheduleOnce() 函数所需的 implicit ActorRef 以获取发件人时产生歧义。

    ActorRef self 存在两次 - 一次在 Actor 中(通常是这样),一次在函数范围内,来自 importcontext。这会导致隐式找不到self,并且由于tell() 默认为DeadLetters,这会导致您遇到的问题。

    如果您注意到the example usage of import context._ in the Akka docs,它是在实例级别完成的,而不是函数级别。这意味着self 替换了Actor 的默认值而不是隐藏它,从而消除了歧义。

    其他选项是仅导入 context.dispatcher 以使 scheduleOnce() 调用工作,或显式传递它。

    import context._ // Option 1
    
    override def receive = {
      case Worked => ???
      case DidNotWork => 
        val target = sender() // Avoid closing over sender().
        //import context.dispatcher // Option 2
        context.system.scheduler.scheduleOnce(500.milliseconds, target, TryAgain)
        //context.system.scheduler.scheduleOnce(500.milliseconds, target, TryAgain)(context.dispatcher) // Option 3
    }
    

    【讨论】:

    • 我不确定我是否同意您的回答。与ActorContext 相关联的唯一两件事是隐式的,因此在导入context._ 后处于范围内的是调度程序和演员所在的ActorSystem。我不认为你对隐式有歧义sender 来自 scheduleOncescheduleOnce 上有一个隐含的 sender:ActorRef arg,但它的默认值为 Actor.noSender,这就是您看到死信的原因。如果你有一个隐含的 ActorRef 在范围内,它会工作得很好
    • 我在一个演员里面 - there is an implicit ActorRef in scope - self。这在我不执行import context._ 时有效。当我在import context._ 呼叫之后尝试访问self 时,我得到Error:(18, 7) reference to self is ambiguous; it is both defined in trait Actor and imported subsequently by import context._ - 所以我很确定我的原因是正确的,但如果你知道得更好,请解释为什么隐式提供通过Actor 不起作用。
    • 你说得对,ActorContext 中的self 不是implicit,但这似乎并不重要。我已经编辑了我的问题,以包含一个完整的示例来显示手头的问题。你可以很清楚地看到context._import 是导致隐式失败的原因。
    【解决方案2】:

    所以在深入挖掘时,我同意context._ 的导入是问题所在。 ActorContext 上有一个self,但不是隐含的。当您将其导入作用域时,它会取代每个 Actor 附带的隐式 self。所以现在你在范围内有一个self ref,但它不再是隐含的,这就是你在那里看到死信的原因。如果您将来遇到这种情况,并且想要完全导入上下文,因为您可以通过将调度移动到 Actor 中的单独方法来轻松解决问题,如下所示:

    class Test extends Actor {
      override def receive = {
        case "badtest" =>
          import context._
          scheduleFromMe("bad")
        case "goodtest" =>
          import context.dispatcher
          scheduleFromMe("good")
        case other => println(s"$sender $other")
      }
    
      def scheduleFromMe(s:String)(implicit ec:ExecutionContext) = context.system.scheduler.scheduleOnce(10.milliseconds, this.self, s)
    }
    

    从这个新方法的隐式参数中删除ActorRef 允许对调度程序的调用总是选择隐式自我ActorRef,即这个演员。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-06
      • 1970-01-01
      • 2021-12-20
      • 2021-08-28
      • 1970-01-01
      • 2021-06-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多