dotnet/reactive · error · ArgumentNullException
ArgumentNullException: action
Error message
ArgumentNullException: action
What it means
After validating the period, SchedulePeriodic validates the action delegate and throws ArgumentNullException (named action) when it is null. The periodic timer created from the DispatcherQueue would have nothing to invoke, so null is rejected up front.
Solutions
- Supply a concrete periodic action; use a no-op body returning the state if nothing needs doing.
- Null-check the callback at the call site before scheduling.
- Fix upstream assignment so the periodic callback is initialized.
Example fix
// before
scheduler.SchedulePeriodic(state, TimeSpan.FromSeconds(30), null);
// after
scheduler.SchedulePeriodic(state, TimeSpan.FromSeconds(30), st => { Heartbeat(st); return st; }); Defensive patterns
Strategy: validation
Validate before calling
if (action == null) throw new ArgumentException("action required");
scheduler.SchedulePeriodic(state, period, action); Type guard
bool Schedulable<TState>(Func<TState, TState>? a) => a is not null;
Try / catch
try { scheduler.SchedulePeriodic(state, period, action); }
catch (ArgumentNullException ex) when (ex.ParamName == "action") { /* fix caller */ } Prevention
- Supply real periodic work or skip scheduling entirely
- Initialize periodic callbacks before constructing timers
- Avoid nullable delegate fields feeding SchedulePeriodic
When it happens
Trigger: Calling scheduler.SchedulePeriodic(state, TimeSpan.FromMinutes(1), null) or a wrapper forwarding a null periodic callback.
Common situations: Periodic poll/heartbeat helpers with optional callbacks; delegates lost during refactoring; test scaffolding passing null placeholders.
Related errors
- action (Parameter 'action')
- action (Parameter 'action')
- action (Value cannot be null)
- ArgumentNullException: action
- ArgumentNullException (default message: Value cannot be…
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/10d99a909ba2efb2.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive.WindowsRuntime/System.Reactive.Concurrency/CoreDispatcherScheduler.cs:249
/// <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>
public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
{
//
// According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid.
// Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there.
//
if (period < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(period));
}
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
var timer = CreateDispatcherQueue().CreateTimer();
var state1 = state;
timer.Tick += (o, e) =>
{
state1 = action(state1);
};
timer.Interval = period;
timer.Start();
return Disposable.Create(() =>
{
var t = Interlocked.Exchange(ref timer, null);
if (t != null)View on GitHub (pinned to 94b5d5ab91)