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

  1. Wrap the load call in Application.Current.Dispatcher.InvokeAsync(...).
  2. Avoid ConfigureAwait(false) on the path that leads to content loading.
  3. Use DispatcherSynchronizationContext for the entire navigation flow.
  4. 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

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


AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13). Data as JSON: /api/errors/70c853ba1d910705. Report an issue: GitHub.