夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

Akka Actor確認(メッセージの応答受信)

こんにちは。

今回はAkka Actorに対して送信したメッセージを受信する方法の確認をしてみます。

1. そのまま返り値は返る?

まず、receiveメソッドの結果返り値を返すように修正し、
呼び出し側でメッセージを送った際に返り値をキャストするようにしてみます。
■HelloWorldActor.scala

class HelloWorldActor(name :String) extends Actor {
  /**
   * Actor初期化時処理
   */
  override def preStart = {println(name + " is started.")  }

  /**
   * メッセージ受信時処理
   */
  def receive = {
    case msg: String  => {
      println("HelloWorldActor: Hello world! " + msg + " My name is " + name)
      "HelloWorldActor: Hello world! " + msg + " My name is " + name
    }
  }

  /**
   * Actor終了時処理
   */
  override def postStop = {println(name + " is stopped.")  }
}

■HelloWorldApp.scala

object HelloWorldApp extends App {
  override def main(args: Array[String]): Unit = {
    val system = ActorSystem.apply("HelloWorldApp")
    val helloWorldActor = system.actorOf(Props.apply(new HelloWorldActor("actor1")), "HelloWorldActor")

    val result1 = helloWorldActor ! """Test1"""
    val result2 = helloWorldActor ! """Test2"""

    println("Test1 result is " + result1)
    println("Test2 result is " + result2)

    Thread.sleep(5000)
    system.shutdown()
  }
}

上記のプログラムを実行してみると結果は下記。
Actorの返り値は返ってきていません。
・・まぁ、Actorの処理自体は非同期で行われるので、考えてみればこれで受け取れるはずはないのですが。

Test1 result is ()
Test2 result is ()
actor1 is started.
HelloWorldActor: Hello world! Test1 My name is actor1
HelloWorldActor: Hello world! Test2 My name is actor1
actor1 is stopped.

2. senderに応答を渡すとどうなる?

というわけで、今度はActorが保持しているsenderに対してメッセージを渡してみます。
■HelloWorldActor.scala

class HelloWorldActor(name :String) extends Actor {
  /**
   * Actor初期化時処理
   */
  override def preStart = {println(name + " is started.")  }

  /**
   * メッセージ受信時処理
   */
  def receive = {
    case msg: String  => {
      println("HelloWorldActor: Hello world! " + msg + " My name is " + name)
      sender ! "HelloWorldActor: Hello world! " + msg + " My name is " + name
    }
  }

  /**
   * Actor終了時処理
   */
  override def postStop = {println(name + " is stopped.")  }
}

修正後、実際に実行してみますと・・?

Test1 result is ()
actor1 is started.
Test2 result is ()
HelloWorldActor: Hello world! Test1 My name is actor1
HelloWorldActor: Hello world! Test2 My name is actor1
[INFO] [07/25/2014 06:29:04.365] [HelloWorldApp-akka.actor.default-dispatcher-3] [akka://HelloWorldApp/deadLetters] Message [java.lang.String] from Actor[akka://HelloWorldApp/user/HelloWorldActor#1270036030] to Actor[akka://HelloWorldApp/deadLetters] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
[INFO] [07/25/2014 06:29:04.365] [HelloWorldApp-akka.actor.default-dispatcher-3] [akka://HelloWorldApp/deadLetters] Message [java.lang.String] from Actor[akka://HelloWorldApp/user/HelloWorldActor#1270036030] to Actor[akka://HelloWorldApp/deadLetters] was not delivered. [2] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
actor1 is stopped.

deadLettersというActorに渡されて処理されなかったよ、というログが出力されます。
実際に下記のページを見てみると、
senderとは「senderを使用することで、アクターが最後に受け取ったメッセージの送信元となるアクターを参照することができる」とあります。
https://sites.google.com/site/scalajp/home/documentation/scala_actor_tutorial

なので、Actorを渡していないのでAkkaがデフォルトのActorであるdeadLettersを設定しており、
deadLettersが受け取っている・・という形になっているようです。

3. Actorを入れ子にしてみると?

「senderを使用することで、アクターが最後に受け取ったメッセージの送信元となるアクターを参照することができる」とあるので、
やはりActorのメッセージを受け取れるのはActor、となるようです。
そのため、親子のActorを作成して試してみます。

■ParentActor.scala

class ParentActor(name: String, child: ActorRef) extends Actor {

  /** メッセージ受信時処理 */
  def receive = {
    case msg: String => {
      println("ParentActor: Received String " + msg + " My name is " + name)
      child ! "Hello world! " + msg + " My name is " + name
    }
    case msg: Int => {
      println("ParentActor: Received Int " + msg + " My name is " + name)
    }
  }

■ChildActor.scala

class ChildActor(name: String) extends Actor {

  /** メッセージ受信時処理 */
  def receive = {
    case msg: String => {
      val message = "ChildActor: Received String " + msg + " My name is " + name
      println(message)
      sender ! message.length
    }
  }
}

■MessageSendApp.scala

object MessageSendApp extends App {
  override def main(args: Array[String]): Unit = {
    val system = ActorSystem.apply("MessageSendApp")
    val childActor = system.actorOf(Props.apply(new ChildActor("child1")))
    val parentActor = system.actorOf(Props.apply(new ParentActor("parent1", childActor)))

    parentActor ! """Test1"""
    parentActor ! """Test2"""

    Thread.sleep(5000)
    system.shutdown()
  }
}

これで実行してみると下記のようになりました。

ParentActor: Received String Test1 My name is parent1
ParentActor: Received String Test2 My name is parent1
ChildActor: Received String Hello world! Test1 My name is parent1 My name is child1
ChildActor: Received String Hello world! Test2 My name is parent1 My name is child1
ParentActor: Received Int 83 My name is parent1
ParentActor: Received Int 83 My name is parent1

ParentActorがChildActorからの応答を受け取っていることがわかります。
ちなみにChildActor側のコードを見てもらえればわかると思いますが、
「senderを使用した」他には特に何も設定を行っていません。
このあたりを自動的にやってくれるのはいいですね。

4. senderには何が入っている?

最後に、senderにはどういう情報が入っているかが気になったので、
ChildActorを修正してsenderの内容を出力してみます。
■ChildActor.scala

class ChildActor(name: String) extends Actor {

  /** メッセージ受信時処理 */
  def receive = {
    case msg: String => {
      val message = "ChildActor: Received String " + msg + " My name is " + name
      println(message)
      println(sender)
      println(sender.getClass)
      sender ! message.length
    }
  }
}

すると・・?

ParentActor: Received String Test1 My name is parent1
ParentActor: Received String Test2 My name is parent1
ChildActor: Received String Hello world! Test1 My name is parent1 My name is child1
Actor[akka://MessageSendApp/user/$b#-1505499634]
class akka.actor.RepointableActorRef
ChildActor: Received String Hello world! Test2 My name is parent1 My name is child1
ParentActor: Received Int 83 My name is parent1
Actor[akka://MessageSendApp/user/$b#-1505499634]
class akka.actor.RepointableActorRef
ParentActor: Received Int 83 My name is parent1

senderには型が「akka.actor.RepointableActorRef」で、Akka上のActorを識別する情報が入っていることがわかりました。

Receiveする際の型を切り替えてやれば、
メッセージのやり取りを延々継続して処理をし続けることも可能になりますね。