Skip to content

ProtocolActor Class

Default IProtocolActor: one mailbox (System.Collections.Concurrent.ConcurrentQueue<> of work items), one loop, one sorted list of pending timers — both owned exclusively by whichever thread is currently running the loop, so neither needs its own lock (FR-RAW-020/021). The loop blocks on a SemaphoreSlim for either new mailbox work or the next timer deadline, whichever comes first, and never polls (FR-RAW-022). Any exception from a posted work item or a fired timer is caught and raised via BackgroundExceptionOccurred; the loop keeps running afterward (FR-RAW-023).

public sealed class ProtocolActor : CanKit.Pro.Actor.IProtocolActor, System.IDisposable

Inheritance Object → ProtocolActor

Implements IProtocolActor, IDisposable

Constructors

ProtocolActor(ActorExecutionMode, SynchronizationContext) Constructor

Creates an actor and immediately starts its mailbox loop under mode.

public ProtocolActor(CanKit.Pro.Actor.ActorExecutionMode mode=CanKit.Pro.Actor.ActorExecutionMode.DedicatedThread, System.Threading.SynchronizationContext? synchronizationContext=null);

Parameters

mode ActorExecutionMode

Execution context for the loop (FR-RAW-024). Defaults to a dedicated thread.

synchronizationContext SynchronizationContext

Required when mode is SynchronizationContext; must be null for every other mode.

Properties

ProtocolActor.IsOnCurrentActor Property

True when <i>the calling thread</i> is currently executing a work item or timer callback belonging to this actor, false otherwise.

public bool IsOnCurrentActor { get; }

Property Value

Boolean

Remarks

Lets a public sync API safely detect that it is already on the actor loop and run the requested work inline instead of routing it through PostAsync(Action) and synchronously waiting on the returned task — which would deadlock the loop against itself. External callers still take the marshal-through-mailbox path exactly as before.

Thread-scoped, deliberately: a Task started from inside a callback runs on a different thread and reports false, because it genuinely is not on the actor and must not mutate actor state inline. (Backing this with an System.Threading.AsyncLocal<> instead would flow the flag into every such task through the ExecutionContext and report true there — the bug this property is most likely to be trusted with preventing.) In SynchronizationContext mode the flag is set on whichever thread the context actually runs the callback on, so it stays correct there too.

Methods

ProtocolActor.Post(Action) Method

Enqueues work to run on the actor's mailbox loop and returns immediately ("tell" / fire-and-forget). If work throws, the exception is caught by the loop and surfaced via BackgroundExceptionOccurred — there is no other way for a fire-and-forget caller to observe it (FR-RAW-023).

public void Post(System.Action work);

Parameters

work Action

Implements Post(Action)

ProtocolActor.PostAsync(Action) Method

Enqueues work to run on the actor's mailbox loop and returns a task that completes once it has run ("ask"). Unlike Post(Action), an exception from work is surfaced through the returned task's fault, not through BackgroundExceptionOccurred — the caller is already positioned to observe it by awaiting.

public System.Threading.Tasks.Task PostAsync(System.Action work);

Parameters

work Action

Implements PostAsync(Action)

Returns

Task

ProtocolActor.PostAsync<T>(Func<T>) Method

Same as PostAsync(Action) but returns work's result.

public System.Threading.Tasks.Task<T> PostAsync<T>(System.Func<T> work);

Type parameters

T

Parameters

work Func<T>

Implements PostAsync<T>(Func<T>)

Returns

Task<T>

ProtocolActor.Schedule(TimeSpan, Action) Method

Schedules callback to run on the actor's mailbox loop once delay has elapsed, using event-driven waiting rather than polling (FR-RAW-022) — suitable for STmin waits, timeout checks, and similar periodic/timed protocol tasks. Disposing the returned handle cancels the callback on a best-effort basis: it will not fire if cancellation is observed before it becomes due, but a callback already in flight on the loop may still complete.

public System.IDisposable Schedule(System.TimeSpan delay, System.Action callback);

Parameters

delay TimeSpan

callback Action

Implements Schedule(TimeSpan, Action)

Returns

IDisposable

Events

ProtocolActor.BackgroundExceptionOccurred Event

Raised whenever a posted work item (via Post(Action)) or a scheduled callback (via Schedule(TimeSpan, Action)) throws — the actor's single, defined channel for background exceptions (FR-RAW-023). The mailbox loop keeps running afterward; one failing item never stops the actor. Never raised for PostAsync(Action)/ PostAsync<T>(Func<T>) failures, which surface through their own returned task instead.

public event EventHandler<Exception>? BackgroundExceptionOccurred;

Implements BackgroundExceptionOccurred

Event Type

EventHandler<Exception>