How to communicate with actors
An actor dispatches an incoming message
- as a communication parameter to its behavior function or
- if it is a
Msg
, it processes it according to the message protocol.
Then it immediately proceeds to the next message if there is one or it waits for it.
by send
If we send
an actor a message (which is not of type Msg
), we cause it to pass it as a communication argument to its behavior:
julia> using Actors
julia> import Actors: spawn, newLink
julia> myactor = spawn(println, "Hello ")
Link{Channel{Any}}(Channel{Any}(32), 1, :default)
julia> send(myactor, "World!");
Hello World!
Actors can send messages to other actors if they have their links:
julia> pserv = spawn(println) # spawn a print server
Link{Channel{Any}}(Channel{Any}(32), 1, :default)
julia> become!(myactor, send, pserv, "Hello "); # cause myactor to use it
julia> send(myactor, "Kermit!");
Hello Kermit!
Actors receive messages implicitly.
receive
Receiving a message is a blocking operation. Since actors are implemented as Julia Task
s, either they are busy in processing a message or waiting for the next message to arrive. We can use receive
explicitly to get messages from actors. To do it, we
- use
newLink
to create a Link, - communicate it to an actor and
- cause it to
send
something to the given link. - Then we can
receive
the actor's message.
julia> me = newLink()
Link{Channel{Any}}(Channel{Any}(32), 1, :local)
julia> become!(myactor, (f, args...)->send(me, f(args...)));
julia> send(myactor, +, 1, 2, 3)
(+, 1, 2, 3)
julia> println("now doing something else ...")
now doing something else ...
julia> receive(me)
6
This is asynchronous bidirectional communication : sender and receiver are decoupled.
- If we call
receive
after the message has been delivered, it will return it immediately. - If we call it before, it will wait until delivery or until it times out.
julia> receive(me)
Actors.Timeout()
If we want to do synchronous communication we combine a send
and receive
to an actor into one code block:
julia> begin
send(myactor, +, 4, 5, 6)
receive(me)
end
15
This will block until the actor responds or until the communication times out.
with the messaging protocol
The messaging protocol is another way to communicate with an actor. It can be used to cause an actor to do also other things e.g. giving information, executing arbitrary functions or updating parameters. Here we demonstrate it briefly with a Call
- Response
pattern:
We cause our actor to assume a +
-behavior. That behavior doesn't send a result back back. But if we send that actor a Call
message with some arguments in a Tuple
, it will send the result as a Response
back to the given link:
julia> become!(myactor, +);
julia> send(myactor, Actors.Call((1,2,3), me))
Actors.Call((1, 2, 3), Link{Channel{Any}}(Channel{Any}(32), 1, :local))
julia> receive(me)
Response(6, Link{Channel{Any}}(Channel{Any}(32), 1, :default))
julia> ans.y
6
A user or programmer can enhance the messaging protocol (see below). You normally won't use it explicitly since we have the user API for that.
use the user API functions
The user API to the messaging protocol provides an easy way to communicate with actors.
request
is a wrapper for synchronous bidirectional communication. It creates a link internally and sends it with the communication parameters as a Call
(or another given message type) to the actor. So it is a shortcut for the above explicit use:
julia> request(myactor, 1,2,3)
6
call
can do both asynchronous and synchronous (bidirectional) communication. If you give it a link, the actor will respond to that:
julia> call(myactor, me, 1, 2, 3)
Actors.Call((1, 2, 3), Link{Channel{Any}}(Channel{Any}(32), 1, :local))
julia> receive(me).y
6
Without a link as second parameter, it will use a request
and work synchronously:
julia> call(myactor, 1, 2, 3)
6
There are more user API functions for accessing the messaging protocol.