dotnet/maui · error · InvalidOperationException
UIThreadRequired
Error message
UIThreadRequired
What it means
DefaultContentLoader.LoadContentAsync throws InvalidOperationException("UIThreadRequired") when Application.Current.Dispatcher.CheckAccess() is false. The default loader touches FrameworkElement content and Application.LoadComponent, both of which must run on the WPF dispatcher thread.
Source
Thrown at src/Compatibility/Core/src/WPF/Interfaces/IContentLoader.cs:20
{
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
public interface IContentLoader
{
Task<object> LoadContentAsync(FrameworkElement parent, object oldContent, object newContent, CancellationToken cancellationToken);
void OnSizeContentChanged(FrameworkElement parent, object content);
}
public class DefaultContentLoader : IContentLoader
{
public Task<object> LoadContentAsync(FrameworkElement parent, object oldContent, object newContent, CancellationToken cancellationToken)
{
if (!Application.Current.Dispatcher.CheckAccess())
throw new InvalidOperationException("UIThreadRequired");
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
return Task.Factory.StartNew(() => LoadContent(newContent), cancellationToken, TaskCreationOptions.None, scheduler);
}
protected virtual object LoadContent(object content)
{
if (content is FrameworkElement)
return content;
if (content is Uri)
return Application.LoadComponent(content as Uri);
if (content is string)
{
if (Uri.TryCreate(content as string, UriKind.RelativeOrAbsolute, out Uri uri))
{
return Application.LoadComponent(uri);View on GitHub (pinned to f377ff1c5e)
Solutions
- Wrap the load call in Application.Current.Dispatcher.InvokeAsync(...).
- Avoid ConfigureAwait(false) on the path that leads to content loading.
- Use DispatcherSynchronizationContext for the entire navigation flow.
- Add a CheckAccess() guard at the top of your navigation helper to fail with a clearer message.
Example fix
// before
await Task.Run(() => loader.LoadContentAsync(parent, old, page, token));
// after
await Application.Current.Dispatcher.InvokeAsync(
() => loader.LoadContentAsync(parent, old, page, token)).Task.ConfigureAwait(true); Defensive patterns
Strategy: validation
Validate before calling
if (!Application.Current.Dispatcher.CheckAccess())
await Application.Current.Dispatcher.InvokeAsync(() => loader.LoadContentAsync(parent, old, page, token)); Type guard
static bool OnUiThread => System.Windows.Application.Current.Dispatcher.CheckAccess();
Try / catch
try { await loader.LoadContentAsync(parent, old, page, token); }
catch (InvalidOperationException ex) when (ex.Message == "UIThreadRequired")
{ await Application.Current.Dispatcher.InvokeAsync(() => loader.LoadContentAsync(parent, old, page, token)); } Prevention
- Use DispatcherSynchronizationContext for the whole navigation flow.
- Avoid background-thread content loads.
- Add a CheckAccess assertion in navigation entry points.
When it happens
Trigger: Calling LoadContentAsync from a non-UI thread (Task.Run, background thread, ConfigureAwait(false) continuation, native callback); content swap driven by a non-dispatcher scheduler.
Common situations: Awaiting IO/network then navigating without dispatcher marshalling; chaining navigation in a Timer.Elapsed handler; multi-window setups where the wrong dispatcher is current.
Related errors
- UIThreadRequired
- ContentLoader
- ContentLoader
- Changing the current page is only allowed if it's being call
- BindableObject was not instantiated on a thread with a dispa
AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13).
Data as JSON: /api/errors/70c853ba1d910705.
Report an issue: GitHub.