Akka Guide
BackgroundGo to Background section
Akka is an actor framework. We'll cover what that means in the next section. Akka.NET is a .NET port of the Scala Akka framework. Akka.NET is an open source project that is actively maintained by the community. Petabridge is one of the main contributors. This guide starts off by describing the actor model, and then goes onto to give some examples of how Akka.NET implements the actor model.
This guide only covers the basics of the actor model and Akka.NET. Basic knowledge of C# is assumed.
public class Example {}
Actor ModelGo to Actor Model section
The actor model is a way of structuring an application. Instead of classes calling each other directly, they communicate via messages via a framework. Classes that communicate in this manner are called Actors. A common analogy is that your application is a factory, your actors are your staff and messages are jobs that need doing.
Things you can do with actors | How this relates to traditional C# |
---|---|
Actors talk to each other by sending messages to each other. | Think of an actor as a function and the message as its parameters. When an actor sends a message to another actor, think of this as one function calling another. |
An actor can also send its self a message. | Think of this as a function calling itself recursively. |
Actors only ever process one message at a time. They have a message inbox/queue that they store messages in until they are ready to process their next message. Actors can only talk to other actors via the sending of messages. When processing a message an actor can modify its own internal private state, but not the state of other actors.
The creation of actors and the sending of messages is done via a framework. There are lots of actor model frameworks, Akka is just one of them.
The actor model gives the following benefits:
- Actors are thread safe. Due to only processing one message at a time)
- Actors are asynchronous. Sending a message to another actor does not block the thread.
- Actors live in a hierarchy which is great for error handling. We'll cover that in the next section.
- The actor model promotes scalable and more loosely coupled systems. Since actors only communicate via messages it is easy to spin up lots of actors to do more work as and when they're needed.
Actor HierarchyGo to Actor Hierarchy section
One of the best features of the actor model is the actor hierarchy. An actor can create other actors. All actors like within the Actor System. When an actor creates another actor the newly created actor is the child actor and the actor that created it is the parent actor. This results in the application having a tree-like structure as follows:
When an actor throws an unhandled exception the parent actor must decide what to do. It has three options:
- Restart all its child actors
- Just restart the failing child actor
- Escalate the error to its parent actor, who in turn has to the same three options.
Things you can do with actors | How this relates to traditional C# |
---|---|
Actors restart their child actors when an unhandled exception occurs | This is used instead of traditional try-catch blocks. |
Actors should always delegate "dangerous" work it child actors.
Actor ReferencesGo to Actor References section
You never instantiate an actor directly, you only ever hold a reference to it.
All actor references are the same regardless of which actor it points to. You can send an actor reference a message and the framework then ensures it reaches the correct actor's queue/inbox. This ensures that:
- You cannot access or modify the internal state of that actor.
- The only way to interact with that actor is to send it a message.
These are two of the most important rules of the actor model and actor references ensure that these rules are always followed.
Actor references are also updated when an actor fails. If a parent restarts an actor the actor reference is still valid and the framework simply sends messages to the new restarted instance.
Akka.NETGo to Akka.NET section
Now it's time to see how Akka.NET allows us to use the actor model in C#.
Actor SystemGo to Actor System section
If actors are created by other actors how do you create your first actor? Akka.NET has an object called the ActorSystem. The system is an object you only create once per application. It is your entry point into Akka. The system can create actors. Since these actors do not have a parent actor they are referred to as root actors.
When creating a system, you must give it a name. It is possible to have actors on two different systems talk to each other over a network. The name is then used for routing purposes. Until you require such functionality don't worry about what name you give to your system.
Below is an example of a console application that starts an actor system and then shuts it down when CTRL + C
is pressed. You will need install the Akka NuGet package. Run the following command in the Package Manager Console Install-Package Akka
.
using System;
using Akka.Actor;
namespace AkkaExamples
{
internal class Program
{
private static ActorSystem _system;
private static void Main(string[] args)
{
_system = ActorSystem.Create("example-name");
Console.CancelKeyPress += (sender, eventArgs) =>
{
eventArgs.Cancel = true; // Cancelling the event prevents the process terminating too early
_system.Terminate();
};
_system.WhenTerminated.Wait();
}
}
}
Notice we don't use the system's constructor. This is a running theme throughout the Akka framework. Most Akka classes have a "Create" instantiation method.
If you run this code you will see a warning stating the "NewtonSoftJsonSerializer has been detected as a default serializer". This warning is nothing to worry about.
Instantiating ActorsGo to Instantiating Actors section
Instantiating an actor in Akka can be done in one of two places:
- In the ActorSystem (for creating root actors)
- Inside Actors (for creating child actors)
To create an actor you need to use the ActorOf method. This method is available in both the ActorSystem and inside actors themselves. The ActorOf method takes a Props object and a name for the actor.
The actor name is used for routing as mentioned previously. Unlike the ActorSystem however, the name is optional for Actors. If you do not provide one, Akka will assign it a GUID as a name at runtime. It is recommended to give your actors a meaningful and descriptive name. It is also worth mentioning that an actor cannot have two children with the same name.
The Props object takes a C# expression in its create method. Akka uses this expression when it starts an Actor. It also uses to it to create new instances when restarting failed actors, hence why it takes an expression and not just an instance of the actor you're instantiating.
Things you can do with actors | How this relates to traditional C# |
---|---|
Actors create other actors by creating Prop objects | This used instead of classes "newing" up other classes. |
The below example instantiates a root actor of a type we haven't created yet called "ExampleRootActor".
using System;
using Akka.Actor;
namespace AkkaExamples
{
internal class Program
{
private static ActorSystem _system;
private static void Main(string[] args)
{
_system = ActorSystem.Create("example-name");
Console.CancelKeyPress += (sender, eventArgs) =>
{
eventArgs.Cancel = true;
_system.Terminate();
};
//Create root actor
IActorRef rootActor = _system.ActorOf(Props.Create(() => new ExampleRootActor()), "example-root-actor-name");
_system.WhenTerminated.Wait();
}
}
}
Note how the result of the ActorOf method is not the actor type we created. It is an IActorRef. As discussed previously the actor reference hides any of the ExampleRootActor's internal state (regardless of whether it is public or private) and the only way to interact with the newly created root actor is to send it messages.
Sending Messages to ActorsGo to Sending Messages to Actors section
Firstly we need to create a message object. Messages are:
- Simple POCO classes
- Immutable
- Serialisable
- Small (if you need to send a very large object between two actors, send a unique identifier such as the object's database ID instead of the large object itself)
Below is an example of a simple message called "ExampleMessage".
namespace AkkaExamples
{
public class ExampleMessage
{
public ExampleMessage(string exampleData)
{
ExampleData = exampleData;
}
public string ExampleData { get; }
}
}
Using constructor injection with public getters is the standard way of creating an immutable message.
Now we can use our actor ref to send the message to our root actor. We do this using the tell method.
...
IActorRef rootActor = _system.ActorOf(Props.Create(() => new ExampleRootActor()), "example-root-actor-name");
// Send message to root actor via actor ref
rootActor.Tell(new ExampleMessage("Example message information"));
...
The Tell method is "fire and forget". It does not block the thread or wait for a response.
Writing your own ActorsGo to Writing your own Actors section
To create your own actor you need to create a C# class and inherit from one of the Akka actor base classes. The ReceiveActor class is the best general purpose base actor that should fulfil your needs the majority of the time.
There are extensions to the Akka framework whereby you must inherit from different base classes when you want to use them. For example if you want your actors to persist their state in a database, you can use the Akka.Persistence extension. To use this extension, you must inherit from the PersistentActor base class in the actors where you want such functionality. This guide will only focus on creating standard actors with the before mentioned ReceiveActor base class.
When creating a receive actor you must configure the actor inside its constructor by telling it what function to call when it receives a certain type of object as a message. Below is an example.
using System;
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleRootActor : ReceiveActor
{
public ExampleRootActor()
{
// Run the following lambda when a message of the type ExampleMessage is sent to this actor
Receive<ExampleMessage>(m => Console.WriteLine(m.ExampleData));
}
public class ExampleMessage
{
public string ExampleData { get; }
public ExampleMessage(string exampleData)
{
ExampleData = exampleData;
}
}
}
}
You use the Receive method inherited from the ReceiveActor base class to assign a lambda that is to be ran whenever a message of a particular type is received by the actor. For the remainder of this guide we will refer to these lambdas as receive callbacks. Unfortunately, you cannot use C# method groups in the Receive method call due to some of the overloads on the Receive method.
Actors simply ignore messages that they do not have a receive callback registered for.
If a class matches the type of two receive callbacks (e.g. if there are two receive callbacks for the same type or for if a type matches two callbacks through inheritance) then only the callback that is registered first is called.
Creating Child ActorsGo to Creating Child Actors section
To create a child actor from inside an actor you use the Actor Context object. When an actor is created inside the actor system, Akka initialises certain properties on the actor. This is another reason why we create Props objects to create actors rather than just newing them up. The context is one of those properties that is populated for us by the framework.
The Context exposes the same ActorOf method we saw in the ActorSystem previously. Below is an example of creating a child actor we haven't written yet called "ExampleChildActor" and then sending it a message.
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleRootActor : ReceiveActor
{
private readonly IActorRef _exampleActorRef;
public ExampleRootActor()
{
// Create child actor
_exampleActorRef = Context.ActorOf(Props.Create(() => new ExampleChildActor()), "example-child-actor");
// send child actor a message
_exampleActorRef.Tell(new ExampleMessage("Example message information"));
}
}
}
Sending Messages to the Parent ActorGo to Sending Messages to the Parent Actor section
You can use the Parent property on an actor's context object to obtain an actor ref to its parent (the actor that created it). You can then use this to send messages to the parent actor. Below is an example where we send a message called "ExampleSuccessMessage" to the actor's parent.
using System;
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleChildActor : ReceiveActor
{
public ExampleChildActor()
{
Receive<ExampleMessage>(m => {
// Process message
Console.WriteLine(exampleMessage.ExampleData);
// Send a new message to parent actor
Context.Parent.Tell(new ExampleSuccessMessage());
});
}
public class ExampleSuccessMessage { }
}
}
Sending Messages to the Current ActorGo to Sending Messages to the Current Actor section
Just as you can send messages to the parent, you can also send messages to the current actor that is processing the message. This is called sending messages to Self. Below is an example where we send a message of the type "ExampleSelfMessage" to the current actor when we receive a message of the type "ExampleMessage".
using System;
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleSelfTellActor : ReceiveActor
{
public ExampleSelfTellActor()
{
Receive<ExampleMessage>(m => ReceiveExampleMessage(m));
Receive<ExampleSelfMessage>(m => ReceiveExampleSelfMessage(m));
}
private void ReceiveExampleMessage(ExampleMessage exampleMessage)
{
// Process message
Console.WriteLine(exampleMessage.ExampleData);
// Send a new message to self
Self.Tell(new ExampleSelfMessage());
}
private void ReceiveExampleSelfMessage(ExampleSelfMessage exampleSelfMessage)
{
// TODO Process message sent to self here
}
public class ExampleMessage
{
public ExampleMessage(string exampleData)
{
ExampleData = exampleData;
}
public string ExampleData { get; }
}
private class ExampleSelfMessage { }
}
}
One of the more common reasons for doing this is that doing so uses the actor's message queue/inbox. When you send a message to yourself the message is put at the back of the message queue meaning the actor can get through some of the other messages in the queue before dealing with the message you just sent to yourself. Actors do not differentiate between messages sent from other actors or themselves.
I tend to nest message classes inside the actor class that predominantly uses them. This makes it clear where the message is used most. This also allows you to make the message class private when the message type is only sent to and from the actor itself, therefore preventing it from being used in other actors. If a message is used by multiple actors then I place them in their own namespace.
Replying to MessagesGo to Replying to Messages section
Just as you can send messages to the parent actor and yourself, you can also send messages to the actor that sent you the message you're currently processing. This is called replying to a message. In the example below we send a message of the type "ExampleReplyMessage" back to any actors that send us a message of the type "ExampleMessage".
using System;
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleReplyActor : ReceiveActor
{
public ExampleReplyActor()
{
Receive<ExampleMessage>(m => {
// Process message
Console.WriteLine(exampleMessage.ExampleData);
// Send a new message to the actor that the sent current message to this actor
Context.Sender.Tell(new ExampleReplyMessage());
});
}
public class ExampleMessage
{
public ExampleMessage(string exampleData)
{
ExampleData = exampleData;
}
public string ExampleData { get; }
}
public class ExampleReplyMessage { }
}
}
Receiving Replied MessagesGo to Receiving Replied Messages section
If when sending a message to an actor, you expect a reply back, you can use the Ask method. This simply returns a C# Task that resolves when the target actor replies with a message of the desired type. It essentially allows you to request a message from the target actor and await a response, a bit like a HTTP request/response. In the example below we:
- Create a child actor in the constructor
- Set up a receive callback that:
- Sends the child actor an ExampleRequestMessage
- Blocks the thread until the child actor replies with an ExampleResponseMessage
using System;
using System.Threading.Tasks;
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleAskActor : ReceiveActor
{
private readonly IActorRef _exampleReplyActorRef;
public ExampleAskActor()
{
ReceiveAsync<Start>(ReceiveStartAsync);
_exampleReplyActorRef = Context.ActorOf(Props.Create(() => new ExampleReplyActor()), "example-reply-actor-name");
}
private async Task ReceiveStartAsync(Start start)
{
ExampleReplyActor.ExampleResponseMessage result = await _exampleReplyActorRef.Ask<ExampleReplyActor.ExampleResponseMessage>(new ExampleReplyActor.ExampleRequestMessage("Hello, World"));
// The following will print "Successfully replying to message: Hello, World"
Console.WriteLine(result.ExampleData);
}
public class Start { }
}
}
Notice that it is possible to use async receive callbacks using the ReceiveAsync method. This is required to await the Task returned from the Ask method. Due to having different overloads compared to the synchronous Receive method, you can use method groups with the ReceiveAsync method.
Also note that you don't have to use the Ask method to receive messages that are a reply. You can also treat them just like any other message and simply receive them as normal, as described previously.
BehavioursGo to Behaviours section
An actor can reallocate its receive callbacks at run time. When an actor changes the callbacks it is currently using, it is said to be switching behaviours. To switch behaviours you use the Become method. Below is an example.
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleBehaviourActor : ReceiveActor
{
public ExampleBehaviourActor()
{
Become(Starting);
}
private void Starting()
{
Receive<ExampleMessageType1>(m => {
//TODO Do something with first message type that would make actor ready for second message type
Become(Started);
});
}
private void Started()
{
Receive<ExampleMessageType2>(m => {
//TODO Do something with second message type
});
}
public class ExampleMessageType1 { }
public class ExampleMessageType2 { }
}
}
The above example has two states: Starting and Started. The actor becomes Starting when it is constructed. In this behaviour the actor only processes messages of the type "ExampleMessageType1". When the actor receives a message of the type "ExampleMessageType1" it becomes Started. In this behaviour the actor only processes messages of the type "ExampleMessageType2".
This results in an actor that :
- Must receive a message of the type "ExampleMessageType1" before it will process messages of the type "ExampleMessageType2".
- Will ignore all messages of type "ExampleMessageType1" apart from the first one it receives.
It is important to note that receive callbacks from previous behaviours are not carried over to new behaviours.
Stashing MessagesGo to Stashing Messages section
Stashing is often used in conjunction with behaviours. It allows you to store messages temporarily in memory until you're ready to process them. To implement stashing in your actor you must implement the IWithUnboundedStash interface. The interface itself simply contains a public property for an IStash object. Akka then watches for actors that implement this interface when it creates them in the ActorOf method. If it sees an Actor that implements the interface it populates the IStash property for you.
To achieve stashing we use two methods on the Stash property:
- The Stash method places the current message that is being processed in the Stash.
- The UnstashAll method places all the messages currently in the stash at the beginning of the actor's message inbox/queue. Below is an example.
using Akka.Actor;
namespace AkkaExamples
{
public class ExampleStashActor : ReceiveActor, IWithUnboundedStash
{
public IStash Stash { get; set; } // Property to implement IWithUnboundedStash
public ExampleStashActor()
{
Become(Starting);
}
private void Starting()
{
Receive<ExampleMessageType1>(m => {
//TODO Do something with first message type that would make actor ready for second message type
Become(Started);
});
Receive<ExampleMessageType2>(m => Stash.Stash()); // Stash ExampleMessageType2 while starting
}
private void Started()
{
Receive<ExampleMessageType2>(m => {
//TODO: do something with second message type
});
Stash.UnstashAll(); // Unstash stashed messages when started
}
public class ExampleMessageType1 { }
public class ExampleMessageType2 { }
}
}
The above example results in an actor that stores all messages of the type "ExampleMessageType2" in its stash until it receives a message of the type "ExampleMessageType1" whereby it unstashes all of the previously stashed messages. The messages are then processed in the same order as to which they were sent. It is important to note that unstashed messages get put at the front of the queue. Petabridge explains this really well in their bootcamp here.
Using Async Functions in AkkaGo to Using Async Functions in Akka section
If you want to use a C# async function in your actor, but don't want to block the thread like ReceiveAsync does, then you can pipe a task's result into a message that is sent to an actor.
This is done using the PipeTo method. PipeTo is an extension method on the Task object. This means you can use it on pretty much all async functions in C#. It allows you to send the result of a task to an actor.
This is the most common and recommended way of using async functions in Akka. In-fact, ReceiveAsync is a relatively new feature in Akka - and before that - piping was the only way to use async functions in Akka. Below is an example:
using System;
using System.Threading.Tasks;
using Akka.Actor;
namespace AkkaExamples
{
public class ExamplePipeToActor : ReceiveActor
{
public ExamplePipeToActor(IThingyDoer thingyDoer)
{
Receive<ExampleRequestMessage>(m => {
thingyDoer
.DoAsync(request.ExampleData)
.ContinueWith(
task => new ExampleResponseMessage(task.Result),
TaskContinuationOptions.ExecuteSynchronously)
.PipeTo(Self);
});
Receive<ExampleResponseMessage>(m => {
Console.WriteLine(response.ExampleData);
});
}
public class ExampleRequestMessage
{
public ExampleRequestMessage(string exampleData)
{
ExampleData = exampleData;
}
public string ExampleData { get; }
}
private class ExampleResponseMessage
{
public ExampleResponseMessage(string exampleData)
{
ExampleData = exampleData;
}
public string ExampleData { get; }
}
}
}
In the above example, we call an async function when we receive an ExampleRequestMessage. We use ContinueWith to map the task's completed result to a message type. We then use the PipeTo function to pipe the message to ourselves. Doing this does not block the thread and simply places an immutable message in our inbox when the task completes.
One import thing to note is that the context property is only valid when an actor is processing a message. If you try and access the context in an async callback then an exception is thrown. This is because the async callbacks are ran later on and the actor could be in the middle of processing a different message by then. If you need to access the context in any of your async callbacks, close over them so you don't need to use the context in your callbacks. There is a great blog post about async in Akka that covers this and more by Petabridge that you can find here.
Bells & WhistlesGo to Bells & Whistles section
Below is an example that covers everything we have covered so far (and one thing we haven't) in one actor:
using System.Net.Http;
using Akka.Actor;
namespace AkkaExamples
{
public class DownloadActor : ReceiveActor, IWithUnboundedStash
{
private HttpClient _client;
public DownloadActor()
{
Become(Idle);
}
public IStash Stash { get; set; }
protected override void PreStart()
{
_client = new HttpClient();
base.PreStart();
}
protected override void PostStop()
{
_client.Dispose();
base.PostStop();
}
private void Idle()
{
Receive<Download>(start =>
{
_client
.GetAsync(start.Url)
.PipeTo(
Self,
Self,
httpRes => new DownloadComplete(httpRes.Content),
exception => new DownloadFailure());
Become(Downloading);
});
}
private void Downloading()
{
ReceiveAsync<DownloadComplete>(async download =>
{
string content = await download.Content.ReadAsStringAsync();
Context.Parent.Tell(new DownloadResult(content));
Stash.UnstashAll();
Become(Idle);
});
Receive<DownloadFailure>(failure =>
{
Context.Parent.Tell(failure);
Stash.UnstashAll();
Become(Idle);
});
Receive<Download>(start => Stash.Stash());
}
public class Download
{
public string Url { get; }
public Download(string url)
{
Url = url;
}
}
private class DownloadComplete
{
public HttpContent Content { get; }
public DownloadComplete(HttpContent content)
{
Content = content;
}
}
public class DownloadResult
{
public string Content { get; }
public DownloadResult(string content)
{
Content = content;
}
}
public class DownloadFailure { }
}
}
The thing we haven't covered yet is actor life cycle hooks. This example uses them to create and dispose of a HTTP client when the actor is started and stopped. You can find out more about the actor life cycle in Akka here.
The above example:
- Stashes requests to download something when it is already in the middle of downloading something. It then goes back to the stashed messages when it is finished.
- Pipes the result of DownloadAsync to itself so the thread isn't blocked while it downloads.
- Uses ReceiveAsync and awaits ReadAsStringAsync rather than using piping. This keeps the code cleaner and piping messages around for something that should not take long can be overkill.
- Sends the parent actor a message when the download has succeeded or failed.
- Uses the overloaded version of PipeTo that returns a different message when the task fails to complete.
DocumentationGo to Documentation section
The main sources of documentation and information for Akka.NET are:
When searching for Akka information online, ensure you always add "net" onto your searches otherwise you'll most probably get results that refer to the Scala version.