Let's install Akka. We add it as a dependency to our build.sbt
file:
scalaVersion := "2.11.7" libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.4.0"
We can now import Akka as follows:
import akka.actor._
For our first foray into the world of actors, we will build an actor that echoes every message it receives. The code examples for this section are in a directory called chap09/hello_akka
in the sample code provided with this book (https://github.com/pbugnion/s4ds):
// EchoActor.scala import akka.actor._ class EchoActor extends Actor with ActorLogging { def receive = { case msg:String => Thread.sleep(500) log.info(s"Received '$msg'") } }
Let's pick this example apart, starting with the constructor. Our actor class must extend Actor
. We also add ActorLogging
, a utility trait that adds the log
attribute.
The Echo
actor exposes a single method, receive
. This is the actor's only way of communicating with the external world. To be useful, all actors must expose a receive
method. The receive
method is a partial function, typically implemented with multiple case
statements. When an actor starts processing a message, it will match it against every case
statement until it finds one that matches. It will then execute the corresponding block.
Our echo actor accepts a single type of message, a plain string. When this message gets processed, the actor waits for half a second and then echoes the message to the log file.
Let's instantiate a couple of Echo actors and send them messages:
// HelloAkka.scala import akka.actor._ object HelloAkka extends App { // We need an actor system before we can // instantiate actors val system = ActorSystem("HelloActors") // instantiate our two actors val echo1 = system.actorOf(Props[EchoActor], name="echo1") val echo2 = system.actorOf(Props[EchoActor], name="echo2") // Send them messages. We do this using the "!" operator echo1 ! "hello echo1" echo2 ! "hello echo2" echo1 ! "bye bye" // Give the actors time to process their messages, // then shut the system down to terminate the program Thread.sleep(500) system.shutdown }
Running this gives us the following output:
[INFO] [07/19/2015 17:15:23.954] [HelloActor-akka.actor.default-dispatcher-2] [akka://HelloActor/user/echo1] Received 'hello echo1' [INFO] [07/19/2015 17:15:23.954] [HelloActor-akka.actor.default-dispatcher-3] [akka://HelloActor/user/echo2] Received 'hello echo2' [INFO] [07/19/2015 17:15:24.955] [HelloActor-akka.actor.default-dispatcher-2] [akka://HelloActor/user/echo1] Received 'bye bye'
Note that the echo1
and echo2
actors are clearly acting concurrently: hello echo1
and hello echo2
are logged at the same time. The second message, passed to echo1
, gets processed after the actor has finished processing hello echo1
.
There are a few different things to note:
Props[T]
. We then ask the actor system to create an actor with these properties. In fact, we never instantiate actors with new
: they are either created by calling the actorOf
method in the actor system or a similar method from within another actor (more on this later).We never call an actor's methods from outside that actor. The only way to interact with the actor is to send messages to it. We do this using the tell operator, !
. There is thus no way to mess with an actor's internals from outside that actor (or at least, Akka makes it difficult to mess with an actor's internals).