AvaloniaUI/Avalonia · critical · InvalidOperationException
This class should be instanciated from the UI thread
Error message
This class should be instanciated from the UI thread
What it means
AndroidDispatcherImpl must be created on the Android UI (main Looper) thread because it posts Runnables to that Looper. The constructor checks CurrentThreadIsLoopThread and throws InvalidOperationException if not. This prevents a dispatcher bound to the wrong thread from the start.
Source
Thrown at src/Android/Avalonia.Android/AndroidDispatcherImpl.cs:33
{
[ThreadStatic] private static bool? s_isUIThread;
private readonly Looper _mainLooper;
private readonly Handler _handler;
private readonly Runnable _signaler;
private readonly Runnable _timerSignaler;
private readonly Runnable _wakeupSignaler;
private readonly MessageQueue _queue;
private int _signaled;
private bool _backgroundProcessingRequested;
public AndroidDispatcherImpl()
{
_mainLooper = App.Context.MainLooper ??
throw new InvalidOperationException(
"Application.Context.MainLooper was not expected to be null.");
if (!CurrentThreadIsLoopThread)
throw new InvalidOperationException("This class should be instanciated from the UI thread");
_handler = new Handler(_mainLooper);
_signaler = new Runnable(OnSignaled);
_timerSignaler = new Runnable(OnTimer);
_wakeupSignaler = new Runnable(() => { });
_queue = Looper.MyQueue();
Looper.MyQueue().AddIdleHandler(new IdleHandler(this));
CanQueryPendingInput = OperatingSystem.IsAndroidVersionAtLeast(23);
}
public event Action? Timer;
private void OnTimer() => Timer?.Invoke();
public event Action? Signaled;
private void OnSignaled()
{
Interlocked.Exchange(ref _signaled, 0);
Signaled?.Invoke();
}View on GitHub (pinned to 11c5427268)
Solutions
- Initialize the AndroidDispatcherImpl (and the Avalonia Android platform) on the main UI thread (Activity.onCreate / Application.onCreate).
- If you must start from another thread, post the initialization to Looper.MainLooper via a Handler.
- Verify CurrentThreadIsLoopThread is true before constructing.
- Audit JNI/native callbacks that may run on binder threads and marshal to the UI thread.
Example fix
// before
if (!CurrentThreadIsLoopThread)
throw new InvalidOperationException("This class should be instanciated from the UI thread");
// after (assert on the looper with a clearer message; typo fixed)
if (Looper.MyLooper() != Looper.MainLooper)
throw new InvalidOperationException(
"AndroidDispatcherImpl must be created on the main UI thread (got thread '" + Environment.CurrentManagedThreadId + "')."); Defensive patterns
Strategy: validation
Validate before calling
// marshal dispatcher construction to the UI thread
if (Looper.MyLooper() != Looper.MainLooper)
new Handler(Looper.MainLooper!).Post(() => new AndroidDispatcherImpl()); Type guard
static bool OnUiThread() => Looper.MyLooper() == Looper.MainLooper;
Try / catch
try { _dispatcher = new AndroidDispatcherImpl(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("UI thread"))
{ /* re-dispatch to main looper and retry */ } Prevention
- Construct the dispatcher on the main UI thread only.
- Post initialization via Handler(Looper.MainLooper) if starting elsewhere.
- Audit JNI/native callback threads.
When it happens
Trigger: Constructing AndroidDispatcherImpl from a background/worker thread or from native code where the current thread is not the main Looper thread. Happens if the platform is initialized off the UI thread (e.g. from a Task, service, or JNI callback thread).
Common situations: Initializing Avalonia in a background thread; calling from a Task continuation or a Timer; embedding AvaloniaAndroid into a host that spawns the dispatcher from a worker; misuse in instrumentation that runs off-main.
Related errors
- Application.Context.MainLooper was not expected to be null.
- Avalonia Application was not initialized. Make sure you have
- Unknown error: AvaloniaView initialization has failed.
- Activity.Window must be set.
- The control isn't currently attached to a toplevel
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/453531b7fb3f1a73.
Report an issue: GitHub.