Table of Contents

Actors

Build a single-consumer actor that mutates state without locks, then talk to it with fire-and-forget and request/response messages.

What you'll build

A CounterActor (SquidStd.Actors) that processes messages one at a time inside ReceiveAsync, driven by TellAsync (fire-and-forget) and AskAsync (request/response).

Prerequisites

  • .NET 10 SDK
  • dotnet add package SquidStd.Actors

Steps

1. Define the message contract and the actor

A marker interface groups the messages; Increment is fire-and-forget, GetTotal derives from ActorRequest<int> to carry a reply. The actor mutates _total without locks because messages are processed one at a time.


// The message contract: a marker interface, a fire-and-forget message, and an ask request.
internal interface ICounterMessage;

internal sealed record Increment(int By) : ICounterMessage;

internal sealed record GetTotal : ActorRequest<int>, ICounterMessage;

// A single-consumer actor: state is mutated without locks inside ReceiveAsync.
internal sealed class CounterActor : Actor<ICounterMessage>
{
    private int _total;

    protected override ValueTask ReceiveAsync(ICounterMessage message, CancellationToken cancellationToken)
    {
        switch (message)
        {
            case Increment increment:
                _total += increment.By;

                break;
            case GetTotal request:
                request.Reply(_total);

                break;
        }

        return ValueTask.CompletedTask;
    }
}

2. Send fire-and-forget messages

TellAsync enqueues a message and returns without waiting for it to be handled.


// Fire-and-forget messages: TellAsync enqueues without awaiting a reply.
await counter.TellAsync(new Increment(5));
await counter.TellAsync(new Increment(3));

3. Ask for a reply

AskAsync<TRequest, TReply> enqueues a request and awaits the typed reply the actor sends with request.Reply(...).


// Request/response: AskAsync enqueues a request and awaits its typed reply.
var total = await counter.AskAsync<GetTotal, int>(new());

Console.WriteLine($"Total: {total}");

Run it

dotnet run --project samples/SquidStd.Samples.Actors

Prints Total: 8.

Next steps