dotnet/maui · error · InvalidOperationException
UIThreadRequired
Error message
UIThreadRequired
What it means
FormsContentLoader.LoadContentAsync throws InvalidOperationException("UIThreadRequired") when Application.Current.Dispatcher.CheckAccess() returns false, i.e. the call is made on a non-UI thread. WPF content loading and renderer creation require the dispatcher thread because they touch FrameworkElements.
Source
Thrown at src/Compatibility/Core/src/WPF/FormsContentLoader.cs:20
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Maui.Controls.Compatibility.Platform.WPF.Interfaces;
namespace Microsoft.Maui.Controls.Compatibility.Platform.WPF
{
public class FormsContentLoader : IContentLoader
{
public Task<object> LoadContentAsync(FrameworkElement parent, object oldContent, object newContent, CancellationToken cancellationToken)
{
VisualElement element = oldContent as VisualElement;
if (element != null)
{
element.Cleanup(); // Cleanup old content
}
if (!System.Windows.Application.Current.Dispatcher.CheckAccess())
throw new InvalidOperationException("UIThreadRequired");
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
return Task.Factory.StartNew(() => LoadContent(parent, newContent), cancellationToken, TaskCreationOptions.None, scheduler);
}
protected virtual object LoadContent(FrameworkElement parent, object page)
{
VisualElement visualElement = page as VisualElement;
if (visualElement != null)
{
var renderer = CreateOrResizeContent(parent, visualElement);
return renderer;
}
return null;
}
public void OnSizeContentChanged(FrameworkElement parent, object page)
{View on GitHub (pinned to f377ff1c5e)
Solutions
- Marshal the call onto the dispatcher: await Application.Current.Dispatcher.InvokeAsync(() => loader.LoadContentAsync(...)).
- Ensure the async chain retains the synchronization context (do not ConfigureAwait(false) before navigation).
- Push navigation work through the Forms main/UI thread abstraction if available.
- Assert Dispatcher.CheckAccess() at the entry of your navigation helpers to fail early.
Example fix
// before
await Task.Run(() => loader.LoadContentAsync(parent, old, page, token));
// after
await Application.Current.Dispatcher.InvokeAsync(
() => loader.LoadContentAsync(parent, old, page, token)); Defensive patterns
Strategy: validation
Validate before calling
if (!Application.Current.Dispatcher.CheckAccess())
await Application.Current.Dispatcher.InvokeAsync(() => LoadAsync());
else
await LoadAsync(); 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
- Marshal navigation onto the dispatcher from non-UI callbacks.
- Avoid ConfigureAwait(false) on the navigation path.
- Assert CheckAccess at the top of navigation helpers.
When it happens
Trigger: Invoking LoadContentAsync from a Task continuation, background thread, async event handler without marshalling, or a Timer callback; navigation triggered by an awaited network/IO callback that resumed on a thread-pool thread.
Common situations: Awaiting without ConfigureAwait(true) on a non-UI context; pushing navigation from a background service; callbacks from native timers; threading violations during page swaps.
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/0839fe2da14d7998.
Report an issue: GitHub.