dotnet/maui · error · InvalidOperationException

Changing the current page is only allowed if it's being call

Error message

Changing the current page is only allowed if it's being called from the same UI thread.Please ensure that the new page is in the same UI thread as the current page.

What it means

SetCurrent catches exceptions during page transitions and checks for HResult 0x8001010E (RPC_E_WRONG_THREAD). When this HResult is detected, it rethrows as InvalidOperationException with a descriptive message. This fires when code attempts to change the current page from a thread other than the UI thread, violating WinUI/UWP threading rules.

Source

Thrown at src/Compatibility/Core/src/Windows/Platform.cs:425

				newPage.Layout(ContainerBounds);

				AddPage(newPage);

				completedCallback?.Invoke();

				_currentPage = newPage;

				UpdateToolbarTracker();

				await UpdateToolbarItems();
			}
			catch (Exception error)
			{
				//This exception prevents the Main Page from being changed in a child 
				//window or a different thread, except on the Main thread. 
				//HEX 0x8001010E 
				if (error.HResult == -2147417842)
					throw new InvalidOperationException("Changing the current page is only allowed if it's being called from the same UI thread." +
						"Please ensure that the new page is in the same UI thread as the current page.");
				throw;
			}
		}

		void RemovePage(Page page)
		{
			if (_container == null || page == null)
				return;

			_modalBackgroundPage?.GetCurrentPage()?.SendAppearing();

			IVisualElementRenderer pageRenderer = GetRenderer(page);

#pragma warning disable RS0030 // Do not use banned APIs; Panel.Children is banned for performance reasons.
			if (_container.Children.Contains(pageRenderer.ContainerElement))
				_container.Children.Remove(pageRenderer.ContainerElement);
#pragma warning restore RS0030 // Do not use banned APIs

View on GitHub (pinned to f377ff1c5e)

Solutions

  1. Marshal navigation calls to the UI thread: Device.BeginInvokeOnMainThread(async () => await Navigation.PushAsync(page))
  2. Use Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => ...) in WinUI code
  3. Capture SynchronizationContext or use ConfigureAwait(true) to ensure continuations run on UI thread
  4. Avoid calling any page-changing API from Task.Run or background event handlers

Example fix

// before
Task.Run(() => {
    // runs on thread pool — throws RPC_E_WRONG_THREAD
    Application.Current.MainPage = new DifferentPage();
});

// after
Device.BeginInvokeOnMainThread(() => {
    Application.Current.MainPage = new DifferentPage();
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Before any page change from async/background context, marshal to UI thread
Device.BeginInvokeOnMainThread(() =>
{
    Application.Current.MainPage = newPage;
});

Try / catch

try
{
    await SetCurrentAsync(newPage);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("same UI thread"))
{
    // Retry on UI thread
    Device.BeginInvokeOnMainThread(() =>
    {
        Application.Current.MainPage = newPage;
    });
}

Prevention

When it happens

Trigger: A background task, async continuation, timer callback, or event handler on a non-UI thread calls navigation methods or directly changes MainPage. The SetCurrent method performs UI operations (Layout, AddPage, UpdateToolbarItems) that require the UI thread.

Common situations: Task.Run callback modifies UI without Dispatcher marshalling; MessagingCenter subscriber runs on background thread and triggers navigation; event from native WinUI component fires on non-UI thread; long-running async operation's continuation loses the SynchronizationContext.

Related errors


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