dotnet/reactive · error · ArgumentNullException

action (Parameter 'action')

Error message

action (Parameter 'action')

What it means

ThreadPoolScheduler.Windows.Schedule<TState>(state, action) throws ArgumentNullException because the 'action' to execute on the Windows thread pool is null. The scheduler wraps the action in a UserWorkItem for the WinRT thread pool, so a null delegate is rejected immediately.

Solutions

  1. Pass a non-null action delegate; initialize the variable holding it.
  2. Throw a descriptive exception in your own code when the delegate is missing instead of letting the scheduler fail.
  3. Skip scheduling entirely when there is no work to run.

Example fix

// before
scheduler.Schedule(state, (Func<IScheduler, int, IDisposable>)null);
// after
scheduler.Schedule(state, (s, st) => { RunWork(st); return Disposable.Empty; });
Defensive patterns

Strategy: validation

Validate before calling

// csharp
if (action == null)
    return Disposable.Empty; // nothing to schedule
scheduler.Schedule(state, action);

Type guard

// csharp
bool CanSchedule<TState>(Func<IScheduler, TState, IDisposable> action) => action is not null;

Try / catch

// csharp
try { d = scheduler.Schedule(state, action); }
catch (ArgumentNullException ex) when (ex.ParamName == "action") { log.Warn("Work item was null"); d = Disposable.Empty; }

Prevention

When it happens

Trigger: Calling ThreadPoolScheduler.Instance.Schedule(state, (scheduler, state) => ...) with a null Func<IScheduler,TState,IDisposable> — typically a null delegate variable or method returning null.

Common situations: Uninitialized fields holding work items; dependency-injected delegates not wired up; refactoring a lambda into a method whose return became null.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Platforms/WinRT/Concurrency/ThreadPoolScheduler.Windows.cs:95

        /// <summary>
        /// Gets the options that configure how work is scheduled.
        /// </summary>
        [Obsolete("If you require the UWP-specific features of ThreadPoolScheduler use the WindowsRuntimeThreadPoolScheduler in the System.Reactive.WindowsRuntime package. This property will be removed in a future version (because UWP applications will end up with the same ThreadPoolScheduler as all other application types).")]
        public WorkItemOptions Options { get; }

        /// <summary>
        /// Schedules an action to be executed.
        /// </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 null.</exception>
        public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
        {
            if (action == null)
                throw new ArgumentNullException(nameof(action));

            var userWorkItem = new UserWorkItem<TState>(this, state, action);

#pragma warning disable CS0618 // Type or member is obsolete.
            // A note on obsolescence:
            //  The compiler complains because this uses Priority and Options. We could mark the
            //  whole method as obsolete, but this would be slightly misleading because when we
            // eventually remove the obsoleted UWP support, this whole ThreadPoolScheduler will
            // be replaced by the non-UWP implementation, and that continues to support this
            // Schedule overload. So the method isn't really obsolete - it will continue to be
            // available to UWP apps even after we've removed all UWP-specific code from
            // System.Reactive.
            // An argument in favour of marking the method as Obsolete anyway is that the
            // behaviour will change once we remove UWP code from System.Reactive. However,
            // the change in behaviour is interesting only if you've specified either
            // priority or options for the work items, and all the public methods we supply
            // for that *are* obsolete. So anyone relying on that behaviour will already have
            // received an obsolescence warning, and should move to WindowsRuntimeThreadPoolScheduler.

View on GitHub (pinned to 94b5d5ab91)