Skip to content
Shiny.NET

Concurrency

Every call to an actor is a turn, and every turn runs on the actor’s own exclusive scheduler. The actor’s code between awaits never overlaps, so you never need a lock. Calls to different actors run in parallel.

What you choose is when a call may start:

default One call at a time. The next waits for the previous one to finish.
[Reentrant] (class) Any call may start while another waits at an await. The actor can call itself.
[AlwaysInterleave] (method) This method starts even while another call is waiting. Use it for status, cancel or health checks.
[ReadOnly] (method) Read-only calls run together, never beside a call that changes state.
[StatelessWorker(MaxLocalWorkers = 4)] (class) Several activations per id, and a call goes to an idle one. No state or reminders (SACT011).
public interface IDownload : IActor
{
Task Start(Uri uri, CancellationToken ct);
[AlwaysInterleave] Task Cancel(); // gets in while Start is awaiting
[ReadOnly] Task<double> Progress(); // many at once, never beside Start
}
[StatelessWorker(MaxLocalWorkers = 4)]
public class ImageResizer : Actor, IImageResizer { ... }

A call that would wait on itself, directly or through other actors (A -> B -> A), throws ActorDeadlockException naming the path instead of hanging. Interleaving calls are exempt, because they can’t deadlock. Remotely, a deadlock surfaces as status 409.

By default a mailbox is unbounded. Set MailboxCapacity and callers wait for room when an actor falls behind:

actors.Configure(o => o.MailboxCapacity = 100);