dotnet/reactive · error · ArgumentNullException

action (Parameter 'action')

Error message

action (Parameter 'action')

What it means

EventLoopScheduler.SchedulePeriodic<TState>(state, period, action) throws ArgumentNullException(nameof(action)) when the periodic action delegate is null. After validating the period, the scheduler checks the action before constructing the PeriodicallyScheduledWorkItem that will invoke it repeatedly.

Solutions

  1. Verify the periodic Func is assigned before calling SchedulePeriodic.
  2. Guard: if (action == null) throw new InvalidOperationException("periodic action not configured").
  3. Register the callback in a constructor/Initialize step guaranteed to run before scheduling.

Example fix

// before
Func<int, int> tick = null;
scheduler.SchedulePeriodic(0, TimeSpan.FromSeconds(1), tick); // throws

// after
Func<int, int> tick = s => s + 1;
scheduler.SchedulePeriodic(0, TimeSpan.FromSeconds(1), tick);
Defensive patterns

Strategy: validation

Validate before calling

if (action is null) throw new ArgumentNullException(nameof(action));
scheduler.SchedulePeriodic(state, period, action);

Type guard

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

Try / catch

try { scheduler.SchedulePeriodic(state, period, action); }
catch (ArgumentNullException ex) when (ex.ParamName == "action")
{
    // register the missing callback
}

Prevention

When it happens

Trigger: Passing (Func<TState, TState>)null as the periodic action — e.g. an uninitialized delegate field, a conditionally assigned lambda, or a factory method returning null.

Common situations: Periodic heartbeat/polling loops wired from configuration where the callback is registered late or via reflection and never assigned; refactorings that changed the delegate type leaving the field null.

Related errors


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

Appendix: source

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

        /// </summary>
        /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
        /// <param name="state">Initial state passed to the action upon the first iteration.</param>
        /// <param name="period">Period for running the work periodically.</param>
        /// <param name="action">Action to be executed, potentially updating the state.</param>
        /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than <see cref="TimeSpan.Zero"/>.</exception>
        /// <exception cref="ObjectDisposedException">The scheduler has been disposed and doesn't accept new work.</exception>
        public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
        {
            if (period < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException(nameof(period));
            }

            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            return new PeriodicallyScheduledWorkItem<TState>(this, state, period, action);
        }

        private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
        {
            private readonly TimeSpan _period;
            private readonly Func<TState, TState> _action;
            private readonly EventLoopScheduler _scheduler;
            private readonly AsyncLock _gate = new();

            private TState _state;
            private TimeSpan _next;
            private MultipleAssignmentDisposableValue _task;

            public PeriodicallyScheduledWorkItem(EventLoopScheduler scheduler, TState state, TimeSpan period, Func<TState, TState> action)
            {

View on GitHub (pinned to 94b5d5ab91)