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.

ProtocolActor(ActorExecutionMode, SynchronizationContext, ActorShutdownPolicy) Constructor

Creates an actor with an explicitly selected shutdown policy. The original two-parameter constructor continues to use DrainDueTimers.

public ProtocolActor(CanKit.Pro.Actor.ActorExecutionMode mode, System.Threading.SynchronizationContext? synchronizationContext, CanKit.Pro.Actor.ActorShutdownPolicy shutdownPolicy);

Parameters

mode ActorExecutionMode

Execution context for the loop (FR-RAW-024).

synchronizationContext SynchronizationContext

Required for SynchronizationContext mode; null otherwise.

shutdownPolicy ActorShutdownPolicy

Whether pending due timers are executed or discarded at shutdown.

Exceptions

ArgumentOutOfRangeException
The shutdown policy is not defined.

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, CancellationToken) 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.Dispose() Method

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

public void Dispose();

Implements Dispose()

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, CancellationToken) 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.

cancellationToken withdraws work for as long as it has not started: a token cancelled before the call, or while the item waits in the mailbox, cancels the returned task at once and the work never runs. Work that has already started is never interrupted — it runs to completion, and cancelling then has no effect on the task's outcome, so the mailbox's single-writer discipline (FR-RAW-021) is not weakened by a half-finished work item.

public System.Threading.Tasks.Task PostAsync(System.Action work, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken));

Parameters

work Action

The work to run on the mailbox loop.

cancellationToken CancellationToken

Withdraws the work while it is still queued.

Implements PostAsync(Action, CancellationToken)

Returns

Task

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

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

public System.Threading.Tasks.Task<T> PostAsync<T>(System.Func<T> work, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken));

Type parameters

T

Parameters

work Func<T>

The work to run on the mailbox loop.

cancellationToken CancellationToken

Withdraws the work while it is still queued.

Implements PostAsync<T>(Func<T>, CancellationToken)

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, CancellationToken)/ PostAsync<T>(Func<T>, CancellationToken) failures, which surface through their own returned task instead.

Those reports are raised on the loop, as part of the callback that failed, so a subscriber may touch the protocol instance's single-writer state there. A report about the actor's own shutdown is the exception: ProtocolActor reports a IDisposable.Dispose that gave up waiting for the loop (TimeoutException) on the thread that called Dispose, while the loop is still inside the callback that would not finish. In the task-based execution modes it also reports a loop that failed outside every callback: on that thread too, or, when the loop fails only after Dispose gave up on it, on the thread the loop ended on, after its callbacks. A subscriber that mutates loop-owned state must not do so from those reports.

public event EventHandler<Exception>? BackgroundExceptionOccurred;

Implements BackgroundExceptionOccurred

Event Type

EventHandler<Exception>