dotnet/reactive · error · ArgumentNullException
scheduler (Value cannot be null)
Error message
scheduler (Value cannot be null)
What it means
The IScheduler.Schedule(Action) extension requires a non-null scheduler and a non-null action. When the scheduler argument is null it throws ArgumentNullException with paramName "scheduler" before doing any work. Rx throws eagerly so scheduling failures surface at the call site rather than inside the scheduler.
Solutions
- Pass a valid scheduler instance such as Scheduler.Default, Scheduler.CurrentThread, Scheduler.ThreadPool, or NewThreadScheduler.Default
- Fix the DI registration or factory so the IScheduler dependency resolves non-null
- Verify the variable holding the scheduler is assigned before the Schedule call
- Both scheduler and action are validated — make sure neither is null
Example fix
// before IScheduler scheduler; scheduler.Schedule(() => DoWork()); // NRE-prone: scheduler is null // after IScheduler scheduler = Scheduler.Default; scheduler.Schedule(() => DoWork());
Defensive patterns
Strategy: validation
Validate before calling
if (scheduler is null || action is null)
throw new InvalidOperationException("Both scheduler and action are required for Schedule.");
scheduler.Schedule(action); Type guard
bool CanSchedule(IScheduler? s, Action? a) => s is not null && a is not null;
Try / catch
try
{
scheduler.Schedule(work);
}
catch (ArgumentNullException ex) when (ex.ParamName == "scheduler")
{
Scheduler.Default.Schedule(work);
} Prevention
- Default scheduler dependencies to Scheduler.Default rather than null
- Check both scheduler and action are non-null when forwarding arguments to Rx scheduling APIs
- Cover scheduler wiring in startup/integration tests so nulls surface before runtime
When it happens
Trigger: Calling scheduler.Schedule(someAction) where the scheduler receiver is null — e.g. Scheduler.Default replaced with null, a null scheduler field/property, or a null returned by a scheduler factory.
Common situations: Uninitialized IScheduler dependencies in services; DI misconfiguration (no IScheduler binding); test setups where a scheduler mock was not wired; code that stored Scheduler.Default in a static that was cleared.
Related errors
- null (Parameter 'scheduler')
- Value cannot be null. (Parameter 'observer')
- Value cannot be null. (Parameter 'values')
- scheduler
- action (Value cannot be null)
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/b99edcedaf6c526f.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Simple.cs:22
using System.Reactive.Disposables;
namespace System.Reactive.Concurrency
{
public static partial class Scheduler
{
/// <summary>
/// Schedules an action to be executed.
/// </summary>
/// <param name="scheduler">Scheduler to execute the action on.</param>
/// <param name="action">Action to execute.</param>
/// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="scheduler"/> or <paramref name="action"/> is <c>null</c>.</exception>
public static IDisposable Schedule(this IScheduler scheduler, Action action)
{
if (scheduler == null)
{
throw new ArgumentNullException(nameof(scheduler));
}
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
// Surprisingly, passing the method group of Invoke will create a fresh
// delegate each time although it's static, while an anonymous
// lambda without the need of a closure will be cached.
// Once Roslyn supports caching delegates for method groups,
// the anonymous lambda can be replaced by the method group again. Until then,
// to avoid the repetition of code, the call to Invoke is left intact.
// Watch https://github.com/dotnet/roslyn/issues/5835
return scheduler.Schedule(action, static (_, a) => Invoke(a));
}
/// <summary>View on GitHub (pinned to 94b5d5ab91)