IRunicModelHookScheduler interface

Namespace Runic.Navigation · Runic.Navigation 0.7.0-preview.5

public interface IRunicModelHookScheduler

Optionally implemented by an IRunicModelContext whose model has thread affinity, such as a UI dispatcher context. A navigator whose RunicNavigator.ModelContext implements this interface runs its hooks through it, each as a separate operation on the model's thread.

Remarks

The navigator schedules target factories, departure guards, initialize and resume hooks, and the disposal of owned content, one operation per invocation. Commits still run in turns (IRunicModelContext.InvokeAsync), as operations separate from any hook. Disposal that can't be scheduled because the context is closed runs on the thread pool.

Hook operations are not turns: they don't count toward a context's turn nesting, they hold no turn across their awaits, and a hook may pump (for example, show a modal dialog), during which commits of other transitions can run.

The navigator detects the scheduler with a type test on the context object it was given. A decorator that wraps a scheduling context must implement and forward this interface too; otherwise it hides the scheduler and hooks run on the thread pool.

Methods

RunHookAsync

Task<T> RunHookAsync<T>(Func<Task<T>> hook, CancellationToken cancellationToken)

Starts hook as a separate operation on the model's thread and returns a task that the scheduler completes itself, from its own TaskCompletionSource created with TaskCreationOptions.RunContinuationsAsynchronously.

Type parameters

T

The hook's result type.

Parameters

hook

The hook to run on the model's thread.

cancellationToken

Cancels the operation before it starts. A started hook observes its own token.

Returns

A task that completes with the hook's result.

Remarks

An implementation never runs hook inline in the calling frame, even when the caller is already on the model's thread. When the hook's own awaits capture a context, they resume on the model's thread. The scheduler should install the model thread's SynchronizationContext for the operation, as a UI dispatcher does for every operation it dispatches. The navigator wraps that context while the hook runs, to tell the hook's own code from other work dispatched inside its frame by a nested message pump. It runs the hook inside a wrapper that catches a synchronous throw, so an exception never escapes into the host's message loop. The outcomes are distinguishable:

  • the context is closed, or closes before the operation starts: ObjectDisposedException;
  • cancellationToken is cancelled before the operation starts: OperationCanceledException;
  • hook throws synchronously, or its task faults or is cancelled: that exception, unchanged.

The outcomes are exclusive. A task that ends with one of the first two means that hook was not invoked and never will be; once hook was invoked, the task ends only with its outcome. The navigator relies on this to run owned content disposal exactly once, falling back to the thread pool when the operation didn't run.