dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'action')

Error message

Value cannot be null. (Parameter 'action')

What it means

EventLoopScheduler.Schedule<TState>(state, dueTime, action) throws ArgumentNullException when the action delegate is null. The scheduler enqueues the action into its internal ready/timer lists, so a null delegate would crash the event loop thread later; the library fails fast at the public entry point instead.

Solutions

  1. Ensure the action delegate passed to Schedule is non-null; check the expression producing it for null results.
  2. Guard the call site: if (action == null) throw/return before calling Schedule.
  3. If scheduling conditionally, skip the Schedule call entirely when no action is available instead of passing null.

Example fix

// before
Func<IScheduler, int, IDisposable> work = config.UseWork ? Work : null;
scheduler.Schedule(0, TimeSpan.FromSeconds(1), work);

// after
if (config.UseWork)
{
    scheduler.Schedule(0, TimeSpan.FromSeconds(1), Work);
}
Defensive patterns

Strategy: validation

Validate before calling

if (action is null) throw new ArgumentNullException(nameof(action));
scheduler.Schedule(state, dueTime, action);

Type guard

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

Try / catch

try { scheduler.Schedule(state, dueTime, action); }
catch (ArgumentNullException ex) when (ex.ParamName == "action")
{
    // log & fix delegate wiring
}

Prevention

When it happens

Trigger: Calling scheduler.Schedule(state, dueTime, (Func<IScheduler, TState, IDisposable>)null) directly, or passing a null delegate through wrappers such as Scheduler.Schedule extension overloads whose action argument was itself null (e.g. an uninitialized Func field or a delegate returned null from a factory).

Common situations: Passing a method group that resolves ambiguously and was cached as null, building schedule lambdas conditionally (if (condition) action = null), or reflection/plugin code that fails to resolve the delegate before scheduling recurring work on a dedicated event-loop thread.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/EventLoopScheduler.cs:141

        #endregion

        #region Public methods

        /// <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>
        /// <exception cref="ObjectDisposedException">The scheduler has been disposed and doesn't accept new work.</exception>
        public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
        {
            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            var due = _stopwatch.Elapsed + dueTime;
            var si = new ScheduledItem<TimeSpan, TState>(this, state, action, due);

            lock (_gate)
            {
                if (_disposed)
                {
                    throw new ObjectDisposedException(nameof(EventLoopScheduler));
                }

                if (dueTime <= TimeSpan.Zero)
                {
                    _readyList.Enqueue(si);
                    _evt.Release();
                }
                else

View on GitHub (pinned to 94b5d5ab91)