The actor model (Wikipedia)

An actor is an entity that you can send messages to. In response to a message an actor can do any of the following:

* Send messages to other actors

* Create new actors

* Set behaviour for the next message it receives, (that is, change its internal state)

Lets say you have a concurrent system where many threads want to access the same resource. Instead of protecting the resource with a lock, mutex or semaphore, you create an actor that handles the resource. Then you send messages to the actor and the actor accesses the resource as it processes the messages one by one.

TPL DataFlow

TPL DataFlow (MSDN) is a library for building asynchronous data processing application. It is well suited for actors because it implements, among other things, message passing.

The sample domain

We are a bank and we have accounts. Each account has a balance. We want to be able to deposit money and check the balance.

C# implementation

We want to implement the system using actors. Let’s start with the messages we are going to send to the actors.

Messages

The basic form of a message is ‘something’ sent to an actor. So lets declare it like this:

public abstract class Message {}

To deposit money we send a deposit message with an amount.

public class Deposit : Message { public decimal Amount { get; set; } }

To check the balance we send a QueryBalance message. The result of the query should be sent as a message to another actor, so we include the receiver in the query message.

public class QueryBalance : Message { public Actor Receiver { get; set; } }

The final message is the result of the balance check.

public class Balance : Message { public decimal Amount { get; set; } }

Actors

We implement an actor using an ActionBlock from the DataFlow library. An ActionBlock has an input queue you can post messages to and an action that will be invoked for each received message.

public abstract class Actor { private readonly ActionBlock<Message> _action; public Actor() { _action = new ActionBlock<Message>(message => { dynamic self = this; dynamic mess = message; self.Handle(mess); }); } public void Send(Message message) { _action.Post(message); } }

In the constructor of the Actor we create an action that will convert the actor and the message to dynamic objects and then call the Handle method. This means that the runtime will look up a method called ‘Handle’ with a parameter of the message type and call it.

An account actor should have a balance and handle the Deposit and the QueryBalance messages.

public class AccountActor : Actor { private decimal _balance; public void Handle(Deposit message) { _balance += message.Amount; } public void Handle(QueryBalance message) { message.Receiver.Send(new Balance {Amount = _balance}); } }

We also need an actor that outputs the result of the balance query

public class OutputActor : Actor { public void Handle(Balance message) { Console.WriteLine("Balance is {0}", message.Amount); } }

The program

Now we can create the actors and send messages to them.

public class Program { public static void Main(string[] args) { var account = new AccountActor(); var output = new OutputActor(); account.Send(new Deposit {Amount = 50}); account.Send(new QueryBalance {Receiver = output}); Console.WriteLine("Done!"); Console.ReadLine(); } }

This program first sends a Deposit message to the account actor. The account actor receives it and increased the balance.

The program then sends a QueryBalance message to the account actor. The account actor receives it and sends a Balance message to the output actor who writes the balance to the console.

If you run this program the output is:

Done!

Balance is 50



Notice that “Done!” is before the balance statement! The program sent the messages and finished before the actors were done processing the messages. How do we know when the actors are done processing all messages?

To solve this we use the completion feature of DataFlow and add a Completion property to the Actor class

public Task Completion { get { _action.Complete(); return action.Completion; } }

This tells the ActionBlock to stop receiving messages and return a Task that will complete when all messages currently in the queue have been processed.

We can now modify the program to wait for the actors to finish.

public class Program { public static void Main(string[] args) { var account = new AccountActor(); var output = new OutputActor(); account.Send(new Deposit {Amount = 50}); account.Send(new QueryBalance {Receiver = output}); account.Completion.Wait(); output.Completion.Wait(); Console.WriteLine("Done!"); Console.ReadLine(); } }

The program now produces this output:

Balance is 50

Done!

Conclusion

TPL DataFlow makes it easy to use actor based programming. This is a very simplistic implementation. It does not handle any errors, like what happens if you pass a message type the actor has no Handle method for, or what happens if an exception is throw during processing of the message. TPL DataFlow has good features to handle exceptions, see http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235570.aspx, so you could add error handling rather easy.