PrismLibrary/Prism · error · InvalidOperationException

Resources.EventAggregatorNotConstructedOnUIThread

Error message

Resources.EventAggregatorNotConstructedOnUIThread

What it means

PubSubEvent.Subscribe with ThreadOption.UIThread requires the EventAggregator to have captured a SynchronizationContext at construction. Prism throws InvalidOperationException with Resources.EventAggregatorNotConstructedOnUIThread when SynchronizationContext is null, because it cannot marshal the handler to the UI thread.

Solutions

  1. Construct the EventAggregator on the UI thread so SynchronizationContext.Current is captured
  2. Subscribe with ThreadOption.BackgroundThread or PublisherThread if UI marshaling is not required
  3. Explicitly set SynchronizationContext.Current before constructing the aggregator (e.g. capture the main thread's context)
  4. Register the EventAggregator via DI on the UI thread during app startup

Example fix

// before (background thread)
var aggregator = new EventAggregator();
myEvent.Subscribe(handler, ThreadOption.UIThread); // throws
// after
Device.BeginInvokeOnMainThread(() => aggregator.GetEvent<MyEvent>().Subscribe(handler, ThreadOption.UIThread));
Defensive patterns

Strategy: try-catch

Validate before calling

if (SynchronizationContext.Current == null)
    throw new InvalidOperationException("Subscribe with ThreadOption.UIThread requires a SynchronizationContext; construct the EventAggregator on the UI thread.");

Type guard

bool CanSubscribeOnUIThread(EventAggregator ea) => ea.GetType() != null && SynchronizationContext.Current != null;

Try / catch

try
{
    myEvent.Subscribe(handler, ThreadOption.UIThread);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("UI"))
{
    myEvent.Subscribe(handler, ThreadOption.BackgroundThread); // fallback
}

Prevention

When it happens

Trigger: Calling event.Subscribe(handler, ThreadOption.UIThread) on an EventAggregator that was constructed on a background thread (or in a context without a SynchronizationContext, e.g. some console/test/worker scenarios).

Common situations: Creating the EventAggregator in a background worker or before the UI framework initializes, unit tests running without a synchronization context, Xamarin/MAUI apps where the aggregator is built in a non-UI bootstrap path.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


AI-assisted analysis of PrismLibrary/Prism@358118cd64 (2026-09-15). Data as JSON: /api/errors/857851ef6956814b. Report an issue: GitHub.

Appendix: source

Thrown at src/Prism.Events/PubSubEvent.cs:85

        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
        /// <para/>
        /// The PubSubEvent collection is thread-safe.
        /// </remarks>
        public virtual SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);

            EventSubscription subscription;
            switch (threadOption)
            {
                case ThreadOption.PublisherThread:
                    subscription = new EventSubscription(actionReference);
                    break;
                case ThreadOption.BackgroundThread:
                    subscription = new BackgroundEventSubscription(actionReference);
                    break;
                case ThreadOption.UIThread:
                    if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
                    subscription = new DispatcherEventSubscription(actionReference, SynchronizationContext);
                    break;
                default:
                    subscription = new EventSubscription(actionReference);
                    break;
            }

            return InternalSubscribe(subscription);
        }

        /// <summary>
        /// Publishes the <see cref="PubSubEvent"/>.
        /// </summary>
        public virtual void Publish()
        {
            InternalPublish();
        }

View on GitHub (pinned to 358118cd64)