Skip to content

BackgroundGo to Background section

Akka is an actor framework. We'll cover what that means in the next section. Akka.NET(opens in a new window) is a .NET port of the Scala Akka(opens in a new window) framework. Akka.NET is an open source project that is actively maintained by the community. Petabridge(opens in a new window) 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:

Actor hierarchy

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.

Actor ref

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.

Actor ref new 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(opens in a new window). 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(opens in a new window) method. This method is available in both the ActorSystem and inside actors themselves. The ActorOf method takes a Props(opens in a new window) 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(opens in a new window). 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(opens in a new window) 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(opens in a new window) 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(opens in a new window) classes. The ReceiveActor(opens in a new window) 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(opens in a new window) extension. To use this extension, you must inherit from the PersistentActor(opens in a new window) 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(opens in a new window) 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(opens in a new window) 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(opens in a new window) 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(opens in a new window) 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(opens in a new window) 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(opens in a new window) 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(opens in a new window) interface. The interface itself simply contains a public property for an IStash(opens in a new window) 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:

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(opens in a new window).

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(opens in a new window) does, then you can pipe a task's result into a message that is sent to an actor.

This is done using the PipeTo(opens in a new window) 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(opens in a new window) 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(opens in a new window).

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(opens in a new window).

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(opens in a new window) 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.