document channel and !!/!!! changes
This commit is contained in:
parent
f770cfca59
commit
f3a7c4149a
8 changed files with 191 additions and 125 deletions
|
|
@ -435,7 +435,7 @@ Here is the master actor::
|
|||
|
||||
A couple of things are worth explaining further.
|
||||
|
||||
First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``?`` to achieve the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now.
|
||||
First, we are passing in a ``java.util.concurrent.CountDownLatch`` to the ``Master`` actor. This latch is only used for plumbing (in this specific tutorial), to have a simple way of letting the outside world knowing when the master can deliver the result and shut down. In more idiomatic Akka code, as we will see in part two of this tutorial series, we would not use a latch but other abstractions and functions like ``Channel``, ``Future`` and ``ask()`` to achieve the same thing in a non-blocking way. But for simplicity let's stick to a ``CountDownLatch`` for now.
|
||||
|
||||
Second, we are adding a couple of life-cycle callback methods; ``preStart`` and ``postStop``. In the ``preStart`` callback we are recording the time when the actor is started and in the ``postStop`` callback we are printing out the result (the approximation of Pi) and the time it took to calculate it. In this call we also invoke ``latch.countDown()`` to tell the outside world that we are done.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Module stability: **SOLID**
|
|||
The `Actor Model <http://en.wikipedia.org/wiki/Actor_model>`_ provides a higher level of abstraction for writing concurrent and distributed systems. It alleviates the developer from having to deal with explicit locking and thread management, making it easier to write correct concurrent and parallel systems. Actors were defined in the 1973 paper by Carl Hewitt but have been popularized by the Erlang language, and used for example at Ericsson with great success to build highly concurrent and reliable telecom systems.
|
||||
|
||||
Defining an Actor class
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
-----------------------
|
||||
|
||||
Actors in Java are created either by extending the 'UntypedActor' class and implementing the 'onReceive' method. This method takes the message as a parameter.
|
||||
|
||||
|
|
@ -238,9 +238,9 @@ which you do by Channel.sendOneWay(msg)
|
|||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof String) {
|
||||
String msg = (String)message;
|
||||
if (msg.equals("Hello") && getContext().getSenderFuture().isDefined()) {
|
||||
if (msg.equals("Hello")) {
|
||||
// Reply to original sender of message using the channel
|
||||
getContext().channel().sendOneWay(msg + " from " + getContext().getUuid());
|
||||
getContext().channel().sendOneWaySafe(msg + " from " + getContext().getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -281,61 +281,36 @@ The 'replyUnsafe' method throws an 'IllegalStateException' if unable to determin
|
|||
}
|
||||
}
|
||||
|
||||
Reply using the sender reference
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If the sender reference (the sender's 'ActorRef') is passed into one of the 'send*' methods it will be implicitly passed along together with the message and will be available in the 'Option<ActorRef> getSender()' method on the 'ActorRef. This means that you can use this field to send a message back to the sender.
|
||||
|
||||
On this 'Option' you can invoke 'boolean isDefined()' or 'boolean isEmpty()' to check if the sender is available or not, and if it is call 'get()' to get the reference. It's important to know that 'getSender().get()' will throw an exception if there is no sender in scope. The same pattern holds for using the 'getSenderFuture()' in the section below.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof String) {
|
||||
String msg = (String)message;
|
||||
if (msg.equals("Hello")) {
|
||||
// Reply to original sender of message using the sender reference
|
||||
// also passing along my own reference (the context)
|
||||
if (getContext().getSender().isDefined)
|
||||
getContext().getSender().get().sendOneWay(msg + " from " + getContext().getUuid(), getContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reply using the sender future
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If a message was sent with the 'sendRequestReply' or 'ask' methods, which both implements request-reply semantics using Future's, then you either have the option of replying using the 'reply' method as above. This method will then resolve the Future. But you can also get a reference to the Future directly and resolve it yourself or if you would like to store it away to resolve it later, or pass it on to some other Actor to resolve it.
|
||||
|
||||
The reference to the Future resides in the 'ActorRef' instance and can be retrieved using 'Option<Promise> getSenderFuture()'.
|
||||
|
||||
Promise is a future with methods for 'completing the future:
|
||||
* completeWithResult(..)
|
||||
* completeWithException(..)
|
||||
|
||||
Here is an example of how it can be used:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof String) {
|
||||
String msg = (String)message;
|
||||
if (msg.equals("Hello") && getContext().getSenderFuture().isDefined()) {
|
||||
// Reply to original sender of message using the sender future reference
|
||||
getContext().getSenderFuture().get().completeWithResult(msg + " from " + getContext().getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Summary of reply semantics and options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* getContext().reply(...) can be used to reply to an Actor or a Future.
|
||||
* getContext().getSender() is a reference to the actor you can reply to, if it exists
|
||||
* getContext().getSenderFuture() is a reference to the future you can reply to, if it exists
|
||||
* getContext().channel() is a reference providing an abstraction to either self.sender or self.senderFuture if one is set, providing a single reference to store and reply to (the reference equivalent to the 'reply(...)' method).
|
||||
* getContext().getSender() and getContext().getSenderFuture() will never be set at the same time, as there can only be one reference to accept a reply.
|
||||
* ``getContext().reply(...)`` can be used to reply to an ``Actor`` or a
|
||||
``Future`` from within an actor; the current actor will be passed as reply
|
||||
channel if the current channel supports this.
|
||||
* ``getContext().channel`` is a reference providing an abstraction for the
|
||||
reply channel; this reference may be passed to other actors or used by
|
||||
non-actor code.
|
||||
|
||||
.. note::
|
||||
|
||||
There used to be two methods for determining the sending Actor or Future for the current invocation:
|
||||
|
||||
* ``getContext().getSender()`` yielded a :class:`Option[ActorRef]`
|
||||
* ``getContext().getSenderFuture()`` yielded a :class:`Option[CompletableFuture[Any]]`
|
||||
|
||||
These two concepts have been unified into the ``channel``. If you need to
|
||||
know the nature of the channel, you may do so using instance tests::
|
||||
|
||||
if (getContext().channel() instanceof ActorRef) {
|
||||
...
|
||||
} else if (getContext().channel() instanceof ActorPromise) {
|
||||
...
|
||||
}
|
||||
|
||||
Promise represents the write-side of a Future, enabled by the methods
|
||||
|
||||
* completeWithResult(..)
|
||||
* completeWithException(..)
|
||||
|
||||
Starting actors
|
||||
---------------
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ The Actor trait contains almost no member fields or methods to invoke, you just
|
|||
#. preRestart
|
||||
#. postRestart
|
||||
|
||||
The ``Actor`` trait has one single member field (apart from the ``log`` field from the mixed in ``Logging`` trait):
|
||||
The ``Actor`` trait has one single member field:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
|
|
@ -344,58 +344,29 @@ The ``reply`` method throws an ``IllegalStateException`` if unable to determine
|
|||
if (self.reply_?(result)) ...// success
|
||||
else ... // handle failure
|
||||
|
||||
Reply using the sender reference
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If the sender is an Actor then its reference will be implicitly passed along together with the message and will end up in the ``sender: Option[ActorRef]`` member field in the ``ActorRef``. This means that you can use this field to send a message back to the sender.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
// receiver code
|
||||
case request =>
|
||||
val result = process(request)
|
||||
self.sender.get ! result
|
||||
|
||||
It's important to know that ``sender.get`` will throw an exception if the ``sender`` is not defined, e.g. the ``Option`` is ``None``. You can check if it is defined by invoking the ``sender.isDefined`` method, but a more elegant solution is to use ``foreach`` which will only be executed if the sender is defined in the ``sender`` member ``Option`` field. If it is not, then the operation in the ``foreach`` method is ignored.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
// receiver code
|
||||
case request =>
|
||||
val result = process(request)
|
||||
self.sender.foreach(_ ! result)
|
||||
|
||||
The same pattern holds for using the ``senderFuture`` in the section below.
|
||||
|
||||
Reply using the sender future
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If a message was sent with the ``!!`` or ``?`` methods, which both implements request-reply semantics using Future's, then you either have the option of replying using the ``reply`` method as above. This method will then resolve the Future. But you can also get a reference to the Future directly and resolve it yourself or if you would like to store it away to resolve it later, or pass it on to some other Actor to resolve it.
|
||||
|
||||
The reference to the Future resides in the ``senderFuture: Option[Promise[_]]`` member field in the ``ActorRef`` class.
|
||||
|
||||
Here is an example of how it can be used:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
case request =>
|
||||
try {
|
||||
val result = process(request)
|
||||
self.senderFuture.foreach(_.completeWithResult(result))
|
||||
} catch {
|
||||
case e =>
|
||||
senderFuture.foreach(_.completeWithException(this, e))
|
||||
}
|
||||
|
||||
|
||||
Summary of reply semantics and options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* ``self.reply(...)`` can be used to reply to an ``Actor`` or a ``Future``.
|
||||
* ``self.sender`` is a reference to the ``Actor`` you can reply to, if it exists
|
||||
* ``self.senderFuture`` is a reference to the ``Future`` you can reply to, if it exists
|
||||
* ``self.channel`` is a reference providing an abstraction to either ``self.sender`` or ``self.senderFuture`` if one is set, providing a single reference to store and reply to (the reference equivalent to the ``reply(...)`` method).
|
||||
* ``self.sender`` and ``self.senderFuture`` will never be set at the same time, as there can only be one reference to accept a reply.
|
||||
* ``self.reply(...)`` can be used to reply to an ``Actor`` or a ``Future`` from
|
||||
within an actor; the current actor will be passed as reply channel if the
|
||||
current channel supports this.
|
||||
* ``self.channel`` is a reference providing an abstraction for the reply
|
||||
channel; this reference may be passed to other actors or used by non-actor
|
||||
code.
|
||||
|
||||
.. note::
|
||||
|
||||
There used to be two methods for determining the sending Actor or Future for the current invocation:
|
||||
|
||||
* ``self.sender`` yielded a :class:`Option[ActorRef]`
|
||||
* ``self.senderFuture`` yielded a :class:`Option[CompletableFuture[Any]]`
|
||||
|
||||
These two concepts have been unified into the ``channel``. If you need to know the nature of the channel, you may do so using pattern matching::
|
||||
|
||||
self.channel match {
|
||||
case ref : ActorRef => ...
|
||||
case f : ActorCompletableFuture => ...
|
||||
}
|
||||
|
||||
Initial receive timeout
|
||||
-----------------------
|
||||
|
|
@ -468,7 +439,7 @@ PoisonPill
|
|||
|
||||
You can also send an actor the ``akka.actor.PoisonPill`` message, which will stop the actor when the message is processed.
|
||||
|
||||
If the sender is a ``Future`` (e.g. the message is sent with ``!!`` or ``?``), the ``Future`` will be completed with an ``akka.actor.ActorKilledException("PoisonPill")``.
|
||||
If the sender is a ``Future`` (e.g. the message is sent with ``?``), the ``Future`` will be completed with an ``akka.actor.ActorKilledException("PoisonPill")``.
|
||||
|
||||
HotSwap
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ Client side usage
|
|||
.. code-block:: scala
|
||||
|
||||
val actor = remote.actorFor("hello-service", "localhost", 2552)
|
||||
val result = actor !! "Hello"
|
||||
val result = (actor ? "Hello").as[String]
|
||||
|
||||
There are many variations on the 'remote#actorFor' method. Here are some of them:
|
||||
|
||||
|
|
@ -394,7 +394,7 @@ Paste in the code below into two sbt concole shells. Then run:
|
|||
|
||||
def run() {
|
||||
val actor = remote.actorFor("hello-service", "localhost", 2552)
|
||||
val result = actor !! "Hello"
|
||||
val result = (actor ? "Hello").as[AnyRef]
|
||||
EventHandler.info("Result from Remote Actor: %s", result)
|
||||
}
|
||||
|
||||
|
|
@ -691,7 +691,7 @@ Using the generated message builder to send the message to a remote actor:
|
|||
|
||||
.. code-block:: scala
|
||||
|
||||
val result = actor !! ProtobufPOJO.newBuilder
|
||||
val resultFuture = actor ? ProtobufPOJO.newBuilder
|
||||
.setId(11)
|
||||
.setStatus(true)
|
||||
.setName("Coltrane")
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@ Step 3: Import the type class module definition and serialize / de-serialize::
|
|||
import BinaryFormatMyActor._
|
||||
|
||||
val actor1 = actorOf[MyActor].start()
|
||||
(actor1 !! "hello").getOrElse("_") should equal("world 1")
|
||||
(actor1 !! "hello").getOrElse("_") should equal("world 2")
|
||||
(actor1 ? "hello").as[String].getOrElse("_") should equal("world 1")
|
||||
(actor1 ? "hello").as[String].getOrElse("_") should equal("world 2")
|
||||
|
||||
val bytes = toBinary(actor1)
|
||||
val actor2 = fromBinary(bytes)
|
||||
actor2.start()
|
||||
(actor2 !! "hello").getOrElse("_") should equal("world 3")
|
||||
(actor2 ? "hello").as[String].getOrElse("_") should equal("world 3")
|
||||
}
|
||||
|
||||
Helper Type Class for Stateless Actors
|
||||
|
|
@ -138,13 +138,13 @@ and use it for serialization::
|
|||
import BinaryFormatMyStatelessActor._
|
||||
|
||||
val actor1 = actorOf[MyStatelessActor].start()
|
||||
(actor1 !! "hello").getOrElse("_") should equal("world")
|
||||
(actor1 !! "hello").getOrElse("_") should equal("world")
|
||||
(actor1 ? "hello").as[String].getOrElse("_") should equal("world")
|
||||
(actor1 ? "hello").as[String].getOrElse("_") should equal("world")
|
||||
|
||||
val bytes = toBinary(actor1)
|
||||
val actor2 = fromBinary(bytes)
|
||||
actor2.start()
|
||||
(actor2 !! "hello").getOrElse("_") should equal("world")
|
||||
(actor2 ? "hello").as[String].getOrElse("_") should equal("world")
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -189,13 +189,13 @@ and serialize / de-serialize::
|
|||
import BinaryFormatMyJavaSerializableActor._
|
||||
|
||||
val actor1 = actorOf[MyJavaSerializableActor].start()
|
||||
(actor1 !! "hello").getOrElse("_") should equal("world 1")
|
||||
(actor1 !! "hello").getOrElse("_") should equal("world 2")
|
||||
(actor1 ? "hello").as[String].getOrElse("_") should equal("world 1")
|
||||
(actor1 ? "hello").as[String].getOrElse("_") should equal("world 2")
|
||||
|
||||
val bytes = toBinary(actor1)
|
||||
val actor2 = fromBinary(bytes)
|
||||
actor2.start()
|
||||
(actor2 !! "hello").getOrElse("_") should equal("world 3")
|
||||
(actor2 ? "hello").as[String].getOrElse("_") should equal("world 3")
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -382,7 +382,7 @@ compiler::
|
|||
When you compile the spec you will among other things get a message builder. You
|
||||
then use this builder to create the messages to send over the wire::
|
||||
|
||||
val result = remoteActor !! ProtobufPOJO.newBuilder
|
||||
val resultFuture = remoteActor ? ProtobufPOJO.newBuilder
|
||||
.setId(11)
|
||||
.setStatus(true)
|
||||
.setName("Coltrane")
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ into a :class:`TestActorRef`.
|
|||
.. code-block:: scala
|
||||
|
||||
val actorRef = TestActorRef(new MyActor)
|
||||
val result = actorRef !! Say42 // hypothetical message stimulating a '42' answer
|
||||
result must be (42)
|
||||
val result = (actorRef ? Say42).as[Int] // hypothetical message stimulating a '42' answer
|
||||
result must be (Some(42))
|
||||
|
||||
As the :class:`TestActorRef` is a subclass of :class:`LocalActorRef` with a few
|
||||
special extras, also aspects like linking to a supervisor and restarting work
|
||||
|
|
@ -259,6 +259,111 @@ Ray Roestenburg has written a great article on using the TestKit:
|
|||
`<http://roestenburg.agilesquad.com/2011/02/unit-testing-akka-actors-with-testkit_12.html>`_.
|
||||
His full example is also available :ref:`here <testkit-example>`.
|
||||
|
||||
Using Multiple Probe Actors
|
||||
---------------------------
|
||||
|
||||
When the actors under test are supposed to send various messages to different
|
||||
destinations, it may be difficult distinguishing the message streams arriving
|
||||
at the :obj:`testActor` when using the :class:`TestKit` as a mixin. Another
|
||||
approach is to use it for creation of simple probe actors to be inserted in the
|
||||
message flows. To make this more powerful and convenient, there is a concrete
|
||||
implementation called :class:`TestProbe`. The functionality is best explained
|
||||
using a small example::
|
||||
|
||||
class MyDoubleEcho extends Actor {
|
||||
var dest1 : ActorRef = _
|
||||
var dest2 : ActorRef = _
|
||||
def receive = {
|
||||
case (d1, d2) =>
|
||||
dest1 = d1
|
||||
dest2 = d2
|
||||
case x =>
|
||||
dest1 ! x
|
||||
dest2 ! x
|
||||
}
|
||||
}
|
||||
|
||||
val probe1 = TestProbe()
|
||||
val probe2 = TestProbe()
|
||||
val actor = Actor.actorOf[MyDoubleEcho].start()
|
||||
actor ! (probe1.ref, probe2.ref)
|
||||
actor ! "hello"
|
||||
probe1.expectMsg(50 millis, "hello")
|
||||
probe2.expectMsg(50 millis, "hello")
|
||||
|
||||
Probes may also be equipped with custom assertions to make your test code even
|
||||
more concise and clear::
|
||||
|
||||
case class Update(id : Int, value : String)
|
||||
|
||||
val probe = new TestProbe {
|
||||
def expectUpdate(x : Int) = {
|
||||
expectMsg {
|
||||
case Update(id, _) if id == x => true
|
||||
}
|
||||
reply("ACK")
|
||||
}
|
||||
}
|
||||
|
||||
You have complete flexibility here in mixing and matching the :class:`TestKit`
|
||||
facilities with your own checks and choosing an intuitive name for it. In real
|
||||
life your code will probably be a bit more complicated than the example given
|
||||
above; just use the power!
|
||||
|
||||
Replying to Messages Received by Probes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The probes keep track of the communications channel for replies, if possible,
|
||||
so they can also reply::
|
||||
|
||||
val probe = TestProbe()
|
||||
val future = probe.ref ? "hello"
|
||||
probe.expectMsg(0 millis, "hello") // TestActor runs on CallingThreadDispatcher
|
||||
probe.reply("world")
|
||||
assert (future.isCompleted && future.as[String] == "world")
|
||||
|
||||
Forwarding Messages Received by Probes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Given a destination actor ``dest`` which in the nominal actor network would
|
||||
receive a message from actor ``source``. If you arrange for the message to be
|
||||
sent to a :class:`TestProbe` ``probe`` instead, you can make assertions
|
||||
concerning volume and timing of the message flow while still keeping the
|
||||
network functioning::
|
||||
|
||||
val probe = TestProbe()
|
||||
val source = Actor.actorOf(new Source(probe)).start()
|
||||
val dest = Actor.actorOf[Destination].start()
|
||||
source ! "start"
|
||||
probe.expectMsg("work")
|
||||
probe.forward(dest)
|
||||
|
||||
The ``dest`` actor will receive the same message invocation as if no test probe
|
||||
had intervened.
|
||||
|
||||
Caution about Timing Assertions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The behavior of :meth:`within` blocks when using test probes might be perceived
|
||||
as counter-intuitive: you need to remember that the nicely scoped deadline as
|
||||
described :ref:`above <TestKit.within>` is local to each probe. Hence, probes
|
||||
do not react to each other's deadlines or to the deadline set in an enclosing
|
||||
:class:`TestKit` instance::
|
||||
|
||||
class SomeTest extends TestKit {
|
||||
|
||||
val probe = TestProbe()
|
||||
|
||||
within(100 millis) {
|
||||
probe.expectMsg("hallo") // Will hang forever!
|
||||
}
|
||||
}
|
||||
|
||||
This test will hang indefinitely, because the :meth:`expectMsg` call does not
|
||||
see any deadline. Currently, the only option is to use ``probe.within`` in the
|
||||
above code to make it work; later versions may include lexically scoped
|
||||
deadlines using implicit arguments.
|
||||
|
||||
CallingThreadDispatcher
|
||||
=======================
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ Here is an example of coordinating two simple counter Actors so that they both i
|
|||
|
||||
...
|
||||
|
||||
counter1 !! GetCount // Some(1)
|
||||
(counter1 ? GetCount).as[Int] // Some(1)
|
||||
|
||||
counter1.stop()
|
||||
counter2.stop()
|
||||
|
|
|
|||
|
|
@ -155,7 +155,22 @@ Client: Sending messages
|
|||
|
||||
Our client wraps each message send in a function, making it a bit easier to use. Here we assume that we have a reference to the chat service so we can communicate with it by sending messages. Messages are sent with the '!' operator (pronounced "bang"). This sends a message of asynchronously and do not wait for a reply.
|
||||
|
||||
Sometimes however, there is a need for sequential logic, sending a message and wait for the reply before doing anything else. In Akka we can achieve that using the '!!' ("bangbang") operator. When sending a message with '!!' we do not return immediately but wait for a reply using a `Future <http://en.wikipedia.org/wiki/Futures_and_promises>`_. A 'Future' is a promise that we will get a result later but with the difference from regular method dispatch that the OS thread we are running on is put to sleep while waiting and that we can set a time-out for how long we wait before bailing out, retrying or doing something else. The '!!' function returns a `scala.Option <http://www.codecommit.com/blog/scala/the-option-pattern>`_ which implements the `Null Object pattern <http://en.wikipedia.org/wiki/Null_Object_pattern>`_. It has two subclasses; 'None' which means no result and 'Some(value)' which means that we got a reply. The 'Option' class has a lot of great methods to work with the case of not getting a defined result. F.e. as you can see below we are using the 'getOrElse' method which will try to return the result and if there is no result defined invoke the "...OrElse" statement.
|
||||
Sometimes however, there is a need for sequential logic, sending a message and
|
||||
wait for the reply before doing anything else. In Akka we can achieve that
|
||||
using the '?' operator. When sending a message with '?' we get back a `Future
|
||||
<http://en.wikipedia.org/wiki/Futures_and_promises>`_. A 'Future' is a promise
|
||||
that we will get a result later but with the difference from regular method
|
||||
dispatch that the OS thread we are running on is put to sleep while waiting and
|
||||
that we can set a time-out for how long we wait before bailing out, retrying or
|
||||
doing something else. This waiting is achieved with the :meth:`Future.as[T]`
|
||||
method, which returns a `scala.Option
|
||||
<http://www.codecommit.com/blog/scala/the-option-pattern>`_ which implements
|
||||
the `Null Object pattern <http://en.wikipedia.org/wiki/Null_Object_pattern>`_.
|
||||
It has two subclasses; 'None' which means no result and 'Some(value)' which
|
||||
means that we got a reply. The 'Option' class has a lot of great methods to
|
||||
work with the case of not getting a defined result. F.e. as you can see below
|
||||
we are using the 'getOrElse' method which will try to return the result and if
|
||||
there is no result defined invoke the "...OrElse" statement.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
|
|
@ -165,7 +180,7 @@ Sometimes however, there is a need for sequential logic, sending a message and w
|
|||
def login = chat ! Login(name)
|
||||
def logout = chat ! Logout(name)
|
||||
def post(message: String) = chat ! ChatMessage(name, name + ": " + message)
|
||||
def chatLog = (chat !! GetChatLog(name)).as[ChatLog]
|
||||
def chatLog = (chat ? GetChatLog(name)).as[ChatLog]
|
||||
.getOrElse(throw new Exception("Couldn't get the chat log from ChatServer"))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue