Useful reading: How the actor model works, by example
What is an actor
The fundamental unit of communication that covers all 3 fundamental units of computation:
- processing
- storage
- communication
It represents an entity that can send/receive messages to/from other actors via a mailbox mechanism. The actor is independent from the other parts of the system and can perform operations concurrently in a native way.
Better than OOP because actors communicate asynchronously instead of through method calls like OOP.
OOP method calls mean you have to wait for a response, which is blocking, and at any point during the method execution something might throw an exception or crash the program entirely. The traditional OOP approach isn’t self-healing on its own; you’d need to wrap it in something that is self-healing, in this case, an Actor.
An actor has an address to receive a message. When it receives a message, it can:
- create other actors,
- send a message to other actors it knows,
- decide what to do with the message it receives.
To use the actor model, you need an Actor System per deployable unit. The Actor System is like a container that houses all your actors.
After you have the Actor System, you can create actors in that system using the actorOf method.
This actor can then send messages using the Tell method.
For a class to become an actor, it needs to inherit from a base class based on the type of actor
you want to create. For an actor that receives a message, it inherits from the ReceiveActor
class.
Akka.NET in a nutshell
Akka.NET is a C# port of the Akka actor model from Scala. It’s about building highly concurrent, distributed, and fault-tolerant systems using the actor model. Actors are objects that:
- Receive messages
- Process one message at a time
- Maintain their own private state safely without locks
Basic pieces
| Concept | What it is |
|---|---|
| ActorSystem | A container for actors; manages their lifecycle. |
| Actor | A lightweight object that processes messages one-by-one. |
| Props | Instructions for creating an actor. |
| IActorRef | A “handle” to interact with an actor (send it messages). |
Tell (!) | Fire-and-forget sending a message to an actor. |
Ask (?) | Send a message and expect a response (Task-based). |
| ReceiveAsync | Async version of handling incoming messages inside an actor. |
| Actor Messages | Plain C# classes used as strongly-typed messages. |
Example: setting up an Akka.NET actor system in a C# API
Imagine a .NET Web API that uses Akka.NET actors to do some background job (e.g., order processing).
1. Install the NuGet packages
dotnet add package Akkadotnet add package Akka.DependencyInjection2. Define messages, plain immutable objects (record, class, etc.):
public record ProcessOrder(int OrderId);public record OrderProcessed(int OrderId);3. Create an actor
using Akka.Actor;using System.Threading.Tasks;
public class OrderProcessorActor : ReceiveActor{ public OrderProcessorActor() { ReceiveAsync<ProcessOrder>(async order => { // simulate work await Task.Delay(500); Console.WriteLine($"Processed order {order.OrderId}"); Sender.Tell(new OrderProcessed(order.OrderId)); }); }}ReceiveAsync<T> lets the actor asynchronously handle a message of type T. Sender is a
reference to who sent the message. Tell sends a reply.
4. Create the ActorSystem and spawn actors (e.g. in Program.cs):
using Akka.Actor;using Akka.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// setup Akkavar serviceProvider = builder.Services.BuildServiceProvider();var bootstrap = BootstrapSetup.Create();var diSetup = DependencyResolverSetup.Create(serviceProvider);var actorSystemSetup = bootstrap.And(diSetup);var actorSystem = ActorSystem.Create("MyActorSystem", actorSystemSetup);
// create actor (Props + ActorOf)var orderProcessor = actorSystem.ActorOf(Props.Create(() => new OrderProcessorActor()), "orderProcessor");ActorOf creates an actor instance from Props.
5. Send messages to actors, from a controller or service, using Tell and Ask:
// Fire-and-forget (Tell)orderProcessor.Tell(new ProcessOrder(123));
// Request-response (Ask)var response = await orderProcessor.Ask<OrderProcessed>(new ProcessOrder(456), TimeSpan.FromSeconds(5));
Console.WriteLine($"Got confirmation for order {response.OrderId}");Full quickflow
- Create
ActorSystem - Define actor classes (subclass
ReceiveActor) - Use
ReceiveAsync<T>inside to handle specific message types - Create actors via
ActorSystem.ActorOf(Props) - Send messages via
Tell(no reply) orAsk(with reply) - Messages are plain classes/records, preferably immutable
Practical integration with a .NET API
If you’re in ASP.NET Core, you usually:
- Register
ActorSystemas a singleton service (like a background service) - Register actor
IActorRefinstances withIServiceCollection - Inject and use actor refs inside controllers
builder.Services.AddSingleton(provider =>{ var actorSystem = ActorSystem.Create("MyActorSystem"); var orderProcessor = actorSystem.ActorOf(Props.Create(() => new OrderProcessorActor()), "orderProcessor"); return orderProcessor;});Then inject into your controller:
public class OrdersController : ControllerBase{ private readonly IActorRef _orderProcessor;
public OrdersController(IActorRef orderProcessor) { _orderProcessor = orderProcessor; }
[HttpPost("/process")] public async Task<IActionResult> ProcessOrder([FromBody] int orderId) { var result = await _orderProcessor.Ask<OrderProcessed>(new ProcessOrder(orderId), TimeSpan.FromSeconds(5)); return Ok(result); }}Cheat sheet
| What you do | Code example |
|---|---|
| Create actor system | ActorSystem.Create("name") |
| Define actor | class MyActor : ReceiveActor {} |
| Create actor instance | system.ActorOf(Props.Create(() => new MyActor())) |
| Fire-and-forget message | actorRef.Tell(new MyMessage()) |
| Request-response message | await actorRef.Ask<MyReply>(new MyRequest(), timeout) |
| Inside actor, receive messages | ReceiveAsync<MyMessage>(async msg => { /* handle */ }) |