dotnet/reactive · error · ArgumentNullException

action (Parameter 'action')

Error message

action (Parameter 'action')

What it means

NewThreadScheduler.Schedule<TState>(state, dueTime, action) throws ArgumentNullException when the action delegate is null. This override creates an EventLoopScheduler per work item and delegates the work to it; a null action would be scheduled with nothing to run. Validation happens immediately before any thread is created.

Solutions

  1. Provide a non-null action delegate to Schedule.
  2. Substitute a no-op action returning Disposable.Empty when there is nothing to do.
  3. Validate arguments at the boundary of your wrapper before calling Schedule.
  4. Trace callers (Subscribe/Timer paths) to find where the null delegate originates.

Example fix

// before
Scheduler.NewThread.Schedule(state, TimeSpan.FromSeconds(1), maybeAction);
// after
if (maybeAction == null) throw new ArgumentNullException(nameof(maybeAction));
Scheduler.NewThread.Schedule(state, TimeSpan.FromSeconds(1), maybeAction);
Defensive patterns

Strategy: validation

Validate before calling

if (action == null)
    throw new ArgumentNullException(nameof(action));
if (dueTime < TimeSpan.Zero)
    dueTime = TimeSpan.Zero;

Type guard

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

Try / catch

try
{
    return Scheduler.NewThread.Schedule(state, dueTime, action);
}
catch (ArgumentNullException ex) when (ex.ParamName == "action")
{
    return Disposable.Empty;
}

Prevention

When it happens

Trigger: Calling Schedule(state, dueTime, action) (TimeSpan overload) on NewThreadScheduler (e.g. Scheduler.NewThread) with a null Func<IScheduler, TState, IDisposable>.

Common situations: Custom operator or extension code forwarding null lambdas; delayed work built from optional callbacks (e.g. timeout handlers) that are absent; migrating code from SyncContext schedulers that tolerated nulls.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/NewThreadScheduler.cs:55

        public NewThreadScheduler(Func<ThreadStart, Thread> threadFactory)
        {
            _threadFactory = threadFactory ?? throw new ArgumentNullException(nameof(threadFactory));
        }

        /// <summary>
        /// Schedules an action to be executed after dueTime.
        /// </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>
        /// <param name="dueTime">Relative time after which to execute the action.</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, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
        {
            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            var scheduler = new EventLoopScheduler(_threadFactory)
            {
                ExitIfEmpty = true
            };
            return scheduler.Schedule(state, dueTime, action);
        }

        /// <summary>
        /// Schedules a long-running task by creating a new thread. Cancellation happens through polling.
        /// </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 IDisposable ScheduleLongRunning<TState>(TState state, Action<TState, ICancelable> action)

View on GitHub (pinned to 94b5d5ab91)