dotnet/reactive · error · ArgumentNullException

action

Error message

action

What it means

CoreDispatcherScheduler.Schedule<TState> throws ArgumentNullException when the action delegate is null. The delegate is what gets queued on the CoreDispatcher via RunAsync; a null delegate cannot be dispatched, so Rx validates before creating the SingleAssignmentDisposable and invoking RunAsync.

Solutions

  1. Pass a valid Func<IScheduler, TState, IDisposable>; return Disposable.Empty for no-op work.
  2. Null-check the delegate before scheduling when it is computed at runtime.
  3. If using Schedule(state, action) overloads, ensure the lambda's body returns an IDisposable.

Example fix

// before
Func<IScheduler, int, IDisposable> work = null;
sched.Schedule(0, work); // ANE
// after
var sched2 = sched.Schedule(0, work ?? ((s, st) => Disposable.Empty));
Defensive patterns

Strategy: validation

Validate before calling

if (action == null) throw new InvalidOperationException("schedule action must not be null");

Type guard

bool Schedulable<TState>(Func<IScheduler, TState, IDisposable> a) => a is not null;

Try / catch

try { sched.Schedule(state, action); } catch (ArgumentNullException ex) when (ex.ParamName == "action") { /* skip or log no-op */ }

Prevention

When it happens

Trigger: Calling scheduler.Schedule(state, null) or Schedule((s, st) => null-literal delegate); also occurs when a computed delegate variable is null due to failed conditional logic.

Common situations: Generic scheduling helpers that build the action dynamically and pass an unset delegate; wrapper code around IScheduler that forwards arguments without validation.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/ce886336573edfab. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Platforms/WinRT/Concurrency/CoreDispatcherScheduler.cs:83

        /// <summary>
        /// Gets the priority at which work is scheduled.
        /// </summary>
        public CoreDispatcherPriority Priority { get; }

        /// <summary>
        /// Schedules an action to be executed on the dispatcher.
        /// </summary>
        /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
        /// <param name="state">State passed to the action to be executed.</param>
        /// <param name="action">Action to be executed.</param>
        /// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
        public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
        {
            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            var d = new SingleAssignmentDisposable();

            var res = Dispatcher.RunAsync(Priority, () =>
            {
                if (!d.IsDisposed)
                {
                    try
                    {
                        d.Disposable = action(this, state);
                    }
                    catch (Exception ex)
                    {
                        //
                        // Work-around for the behavior of throwing from RunAsync not propagating
                        // the exception to the Application.UnhandledException event (as of W8RP)
                        // as our users have come to expect from previous XAML stacks using Rx.

View on GitHub (pinned to 94b5d5ab91)