public sealed class DispatcherModelContext : IRunicModelContext, IAsyncDisposable, IRunicModelHookScheduler, IRunicModelContextLifetime, IDisposable A model context on a WPF Dispatcher: turns and navigation hooks run on the UI thread, and code on the UI thread counts as executing.
Remarks
Turns requested on the UI thread run inline. Turns requested on another thread are dispatched, and their exceptions complete the caller's task; they never reach Dispatcher.UnhandledException. Hooks run through IRunicModelHookScheduler as their own dispatcher operations, never inline in the caller, and their awaits resume on the UI thread.
A turn must not pump: no ShowDialog, MessageBox or Wait() inside a turn, a commit's PropertyChanged handler or an OnCommitted action. A turn dispatched inside another turn's nested pump is logged once as event 1081. A hook may pump.
The context closes when it is disposed or when its dispatcher starts shutting down. Pending and later requests then fail with ObjectDisposedException, and DispatcherModelContext.TryPost returns false. Disposing the context never shuts the dispatcher down. Hooks run under a dispatcher SynchronizationContext that moves their continuations to the thread pool once the dispatcher shuts down, so a hook that awaits past shutdown still finishes.
Constructors
DispatcherModelContext
public DispatcherModelContext(Dispatcher dispatcher, DispatcherPriority priority = (DispatcherPriority)9, ILogger<DispatcherModelContext>? logger = null) Creates a model context that runs turns and hooks on dispatcher.
Parameters
dispatcherThe UI thread's dispatcher.
priorityThe priority of dispatched turns and hooks.
loggerThe logger, or
nullforTraceoutput.
Exceptions
InvalidOperationExceptionThe dispatcher has started shutting down.
Properties
Closed
public CancellationToken Closed { get; } Gets a token that is cancelled when the context closes, on disposal or when the dispatcher starts shutting down.
Remarks
A navigator subscribes to it, so requests made after the close end NavigationRejection.Closed at once. Callbacks run synchronously on the closing thread, before pending work is rejected; their exceptions are reported like a failing posted turn's, through DispatcherModelContext.UnhandledTurnException or event 1080.
Dispatcher
public Dispatcher Dispatcher { get; } Gets the dispatcher that runs this context's turns and hooks.
IsExecuting
public bool IsExecuting { get; } Gets whether the context is open and the caller is on its UI thread. All UI-thread code is serialized with the turns, so it counts as executing.
Methods
Dispose
public void Dispose() Closes the context: later requests fail with ObjectDisposedException, pending invocations and hooks fail with it, and pending posted turns are reported as dropped. A running turn is not interrupted.
Remarks
Dropped posted turns are reported synchronously, on the thread that closes the context, through DispatcherModelContext.UnhandledTurnException, or logged as event 1084 without a handler. A hook still running keeps running; once the dispatcher shuts down, its continuations run on the thread pool.
DisposeAsync
public ValueTask DisposeAsync() Closes the context like DispatcherModelContext.Dispose; completes at once.
InvokeAsync
public ValueTask InvokeAsync(Action turn, CancellationToken cancellationToken = default) Runs a short synchronous turn in this context.
Documentation from IRunicModelContext.InvokeAsync.
InvokeAsync
public ValueTask<T> InvokeAsync<T>(Func<T> turn, CancellationToken cancellationToken = default) Runs a short synchronous turn in this context and returns its result.
Documentation from IRunicModelContext.InvokeAsync.
RunHookAsync
public 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
TThe hook's result type.
Parameters
hookThe hook to run on the model's thread.
cancellationTokenCancels 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; cancellationTokenis cancelled before the operation starts:OperationCanceledException;hookthrows 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.
Documentation from IRunicModelHookScheduler.RunHookAsync.
TryPost
public bool TryPost(Action turn) Queues a short synchronous turn when the context is still accepting work.
Documentation from IRunicModelContext.TryPost.
Events
UnhandledTurnException
public event Action<Exception>? UnhandledTurnException Receives exceptions thrown by turns queued with IRunicModelContext.TryPost, and an ObjectDisposedException for each posted turn dropped by disposal.
Documentation from IRunicModelContext.UnhandledTurnException.