dotnet/reactive · error · ArgumentNullException
Value cannot be null. (Parameter 'action')
Error message
Value cannot be null. (Parameter 'action')
What it means
ControlScheduler.Schedule<TState>(state, action) invokes the action on the scheduler's control thread and therefore requires a non-null action delegate. The ArgumentNullException guard runs before the IsDisposed check, so a null action always throws even if the control is already disposed.
Solutions
- Pass a non-null delegate: scheduler.Schedule(state, (sched, st) => { ...; return Disposable.Empty; }).
- Guard the delegate before scheduling and fall back to a no-op returning Disposable.Empty.
- Verify argument order in the Schedule call so a state value is not passed where the Func is expected.
Example fix
// before scheduler.Schedule(state, _work); // _work null // after var work = _work ?? ((sched, st) => Disposable.Empty); scheduler.Schedule(state, work);
Defensive patterns
Strategy: validation
Validate before calling
if (action == null) action = (sched, st) => Disposable.Empty; scheduler.Schedule(state, action);
Type guard
static bool HasWork<TState>(Func<IScheduler, TState, IDisposable> a) => a is not null;
Try / catch
try { scheduler.Schedule(state, action); } catch (ArgumentNullException ex) when (ex.ParamName == "action") { /* substitute no-op or log */ } Prevention
- Initialize delegate fields at declaration or in the constructor
- Check argument order in Schedule calls
- Use Observer/Disposable helpers for default no-op delegates
When it happens
Trigger: Calling scheduler.Schedule(state, null) directly, or Rx operators receiving a null work delegate, e.g. a lambda variable that was never assigned or a Func field left null after refactoring.
Common situations: Building schedules dynamically from a dictionary of delegates where the lookup missed; passing a method group whose containing object method is conditionally compiled out; typo passing wrong argument order so null lands in the action parameter.
Related errors
- Value cannot be null. (Parameter 'control')
- Value cannot be null. (Parameter 'action')
- Value cannot be null. (Parameter 'source')
- Value cannot be null. (Parameter 'scheduler')
- scheduler
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/e70561c70164b038.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Platforms/Desktop/Concurrency/ControlScheduler.cs:48
/// <summary>
/// Gets the control associated with the ControlScheduler.
/// </summary>
public Control Control => _control;
/// <summary>
/// Schedules an action to be executed on the message loop associated with the control.
/// </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 null.</exception>
public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
if (_control.IsDisposed)
{
return Disposable.Empty;
}
var d = new SingleAssignmentDisposable();
_control.BeginInvoke(new Action(() =>
{
if (!_control.IsDisposed && !d.IsDisposed)
{
d.Disposable = action(this, state);
}
}));
return d;View on GitHub (pinned to 94b5d5ab91)