DispatcherModelContext class

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

public sealed class DispatcherModelContext : IRunicModelContext, IAsyncDisposable, IRunicModelHookScheduler, IRunicModelContextLifetime, IDisposable

Inherits or implements 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

dispatcher

The UI thread's dispatcher.

priority

The priority of dispatched turns and hooks.

logger

The logger, or null for Trace output.

Exceptions

InvalidOperationException

The 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

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.

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.