This is an excerpt from the Scala Cookbook. This is Recipe 13.3, “How to communicate (send messages) between Scala/Akka actors.”
Problem
You’re building a Scala actor-based application and want to send messages between actors.
Solution
Actors should be sent immutable messages with the !
method.
When an Akka actor receives a message from another actor, it also receives an implicit reference named sender
, and it can use that reference to send a message back to the originating actor.
The general syntax to send a message to an actor is:
actorInstance ! message
For example, if you have an Actor
instance named car
, you can send it a start
message like this:
car ! "start"
In this case, the message is the String
literal start
. The car
actor should receive this message in a match
expression in its receive
method, and from there it can send a message back to whoever sent the start
message. A simplified version of a receive
method for car
might look like this:
def receive = { case "start" => val result = tryToStart() sender ! result case _ => // do nothing }
As mentioned, the sender
instance is implicitly made available to your actor. If you just want to send a message back to the code that sent you a message, that’s all you have to do.
Discussion
To demonstrate a more complicated example of actors communicating, the following code shows how to send messages back and forth between Akka actors. It was inspired by the “Ping Pong” threading example in the book, The Java Programming Language (Addison-Wesley Professional), by Ken Arnold, James Gosling, and David Holmes:
import akka.actor._ case object PingMessage case object PongMessage case object StartMessage case object StopMessage class Ping(pong: ActorRef) extends Actor { var count = 0 def incrementAndPrint { count += 1; println("ping") } def receive = { case StartMessage => incrementAndPrint pong ! PingMessage case PongMessage => incrementAndPrint if (count > 99) { sender ! StopMessage println("ping stopped") context.stop(self) } else { sender ! PingMessage } case _ => println("Ping got something unexpected.") } } class Pong extends Actor { def receive = { case PingMessage => println(" pong") sender ! PongMessage case StopMessage => println("pong stopped") context.stop(self) case _ => println("Pong got something unexpected.") } } object PingPongTest extends App { val system = ActorSystem("PingPongSystem") val pong = system.actorOf(Props[Pong], name = "pong") val ping = system.actorOf(Props(new Ping(pong)), name = "ping") // start the action ping ! StartMessage // commented-out so you can see all the output //system.shutdown }
Actors should communicate by sending immutable messages between each other. In this case there are four messages, and they’re defined using case
objects: PingMessage
, PongMessage
, StartMessage
, and StopMessage
.
The PingPongTest
object performs the following work:
- Creates an
ActorSystem
. - Creates
pong
, an instance of thePong
actor. (Thepong
object is actually an instance ofActorRef
, though I loosely refer to it as an actor, or actor instance.) ThePong
actor constructor does not require any arguments, so the no-argsProps
syntax is used. - Creates
ping
, an instance of thePing
actor. ThePing
actor constructor takes one argument, anActorRef
, so a slightly different version of theProps
syntax is used. - Starts the ping/pong action by sending a
StartMessage
to theping
actor. Onceping
receives theStartMessage
, the actors send messages back and forth between each other as fast as they can until the counter limit inping
is reached. Messages are sent using the usual!
method.
To get things started, the Ping
class needs an initial reference to the Pong
actor, but once the action starts, the two actors just send a PingMessage
and PongMessage
to each other using the sender
references they implicitly receive, until the Ping
actor count limit is reached. At that time, it sends a StopMessage
to the Pong
actor, and then both actors call their context.stop
methods. The context
object is implicitly available to all actors, and can be used to stop actors, among other uses.
In addition to demonstrating how to communicate between actors using immutable messages, this example provides several examples of an ActorRef
. The ping
and pong
instances are ActorRef
instances, as is sender
.
A great thing about an ActorRef
is that it hides the Actor
instance from you. For instance, the Pong
actor can’t directly execute ping.incrementAndPrint
; the two actors can only send messages between each other. Although this seems limiting at first, once you understand the model, you’ll see that it’s a terrific way to safely implement concurrency in your applications.
Messages can also be sent between actors using the ?
or ask
methods, but those should be used only rarely. See Recipe 13.10, “Sending a Message to an Actor and Waiting for a Reply” for examples of those methods.
The Scala Cookbook
This tutorial is sponsored by the Scala Cookbook, which I wrote for O’Reilly:
You can find the Scala Cookbook at these locations: