Keep state propagation reactive while guaranteeing UI-thread correctness.
All visual tree and control property updates must run on Dispatcher.UIThread.
Primary APIs:
Dispatcher.UIThreadInvoke(...)InvokeAsync(...)Post(...)Use:
Post for non-blocking UI notifications.InvokeAsync when caller must await completion.Invoke only for short synchronous operations.InvokeAsync(...) returns DispatcherOperation / DispatcherOperation<T>.
High-value APIs:
Status (Pending, Executing, Completed, Aborted)Priority (can be adjusted while queued)Abort()GetTask() / GetAwaiter()Completed / Aborted eventsWait() (use with care; avoid blocking UI thread)Pattern:
var op = Dispatcher.UIThread.InvokeAsync(
() => ApplyUiDelta(snapshot),
DispatcherPriority.Background);
if (cancellationToken.IsCancellationRequested)
op.Abort();
await op.GetTask();
Guideline:
await op.GetTask() over Wait() in async code paths.Wait() as a narrow escape hatch for synchronous boundaries.Useful priorities in app code:
Send: immediate synchronous work (already on UI thread).Normal / Default: most UI updates.Input: input-adjacent work.Render / Loaded: render/layout-adjacent work.Background / ContextIdle / ApplicationIdle: non-urgent UI updates.Keep priority choices stable and intentional. Escalating everything to high priority causes responsiveness regressions.
Dispatcher.UIThread.AwaitWithPriority(...) lets you continue on the dispatcher with explicit priority after a task completes.
await Dispatcher.UIThread.AwaitWithPriority(loadTask, DispatcherPriority.Background);
// Continuation is now queued on UI dispatcher at Background priority.
UpdateUiFromLoadedData();
Use this when you need deterministic continuation priority instead of default scheduler behavior.
For scheduled UI work:
DispatcherTimer.Run(...)DispatcherTimer.RunOnce(...)Start(), Stop(), Interval, IsEnabled, TickIDisposable heartbeat = DispatcherTimer.Run(
action: () =>
{
UpdateClockText();
return !viewModel.IsDisposed;
},
interval: TimeSpan.FromSeconds(1),
priority: DispatcherPriority.Background);
Dispose returned timer handles during teardown to avoid stale callbacks.
Dispatcher exposes exception hooks for invoked delegates:
UnhandledExceptionFilterUnhandledExceptionUse them as a final safety net and telemetry path, not as normal control flow.
Useful entry points:
AvaloniaObjectExtensions.GetObservable(...)GetBindingObservable(...)GetPropertyChangedObservable(...)ToBinding()Example: bind observable stream to UI property
IDisposable subscription = textBlock.Bind(
TextBlock.TextProperty,
viewModel.StatusObservable);
Example: observe property changes
IDisposable subscription = textBox
.GetObservable(TextBox.TextProperty)
.Subscribe(value =>
{
// Transform/validate or dispatch commands.
});
Command/input APIs:
ICommandSourceKeyGestureKeyBindingHotkeyManagerRouting APIs:
RoutedEventRoutedEventArgsInteractive.AddHandler(...)Use:
public async Task RefreshAsync()
{
IsBusy = true;
try
{
var dto = await _api.GetDataAsync();
await Dispatcher.UIThread.InvokeAsync(() =>
{
Items.Clear();
foreach (var item in dto.Items)
Items.Add(item);
});
}
finally
{
IsBusy = false;
}
}
Dispatcher.UIThread.Dispatcher.Invoke.
Default mode:
XAML-first references:
x:DataType, {CompiledBinding ...}, control property bindingsXAML-first usage example:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
x:Class="MyApp.Views.StatusView"
x:DataType="vm:StatusViewModel">
<StackPanel Spacing="8">
<TextBlock Text="{CompiledBinding StatusText}" />
<ProgressBar IsIndeterminate="{CompiledBinding IsBusy}" />
</StackPanel>
</UserControl>
Code-only alternative (on request):
textBlock.Bind(TextBlock.TextProperty, viewModel.StatusObservable);
progressBar.Bind(ProgressBar.IsIndeterminateProperty, viewModel.IsBusyObservable);