dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'scheduler')

Error message

Value cannot be null. (Parameter 'scheduler')

What it means

ArgumentNullException thrown by Synchronization.SubscribeOn<TSource>(IObservable<TSource>, IScheduler) when the scheduler argument is null. The scheduler is required to move the subscription onto a thread; without it the operation has no meaning, so the method validates it immediately after validating the source.

Solutions

  1. Pass a concrete IScheduler such as Scheduler.ThreadPool, Scheduler.NewThread, Scheduler.Default, or a test scheduler like TestScheduler.
  2. Fix DI or instance initialization so the scheduler dependency is provided before SubscribeOn is called.
  3. Null-check the scheduler and fall back to Scheduler.Default or Scheduler.Immediate when none is configured.
  4. Use the SynchronizationContext overload instead if a context is available and the scheduler is not.
  5. Verify the scheduler-producing factory or config lookup cannot return null in your environment.

Example fix

// before
var scheduled = source.SubscribeOn(_configuredScheduler); // null
// after
var scheduled = source.SubscribeOn(_configuredScheduler ?? Scheduler.Default);
Defensive patterns

Strategy: validation

Validate before calling

if (scheduler == null) scheduler = Scheduler.Default;
var scheduled = source.SubscribeOn(scheduler);

Type guard

bool IsUsableScheduler(IScheduler scheduler) => scheduler is not null;

Try / catch

try
{
    var scheduled = source.SubscribeOn(scheduler);
}
catch (ArgumentNullException ex) when (ex.ParamName == "scheduler")
{
    var scheduled = source.SubscribeOn(Scheduler.Default);
}

Prevention

When it happens

Trigger: Calling source.SubscribeOn(scheduler) where the IScheduler is null — e.g. an injected scheduler dependency that was not registered in DI, a scheduler factory returning null, or a previously implicit default-scheduler overload migrated to an explicit null argument.

Common situations: DI misconfiguration (IScheduler not registered); unit tests passing null schedulers; Rx version migrations where default scheduler overloads were removed and callers now pass null explicitly.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/Synchronization.cs:40

        /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
        /// <param name="source">Source sequence.</param>
        /// <param name="scheduler">Scheduler to perform subscription and unsubscription actions on.</param>
        /// <returns>The source sequence whose subscriptions and unsubscriptions happen on the specified scheduler.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="scheduler"/> is <c>null</c>.</exception>
        /// <remarks>
        /// Only the side-effects of subscribing to the source sequence and disposing subscriptions to the source sequence are run on the specified scheduler.
        /// In order to invoke observer callbacks on the specified scheduler, e.g. to offload callback processing to a dedicated thread, use <see cref="Synchronization.ObserveOn{TSource}(IObservable{TSource}, IScheduler)"/>.
        /// </remarks>
        public static IObservable<TSource> SubscribeOn<TSource>(IObservable<TSource> source, IScheduler scheduler)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

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

            return new SubscribeOnObservable<TSource>(source, scheduler);
        }

        private sealed class SubscribeOnObservable<TSource> : ObservableBase<TSource>
        {
            private sealed class Subscription : IDisposable
            {
                private SerialDisposableValue _cancel;

                public Subscription(IObservable<TSource> source, IScheduler scheduler, IObserver<TSource> observer)
                {
                    _cancel.TrySetFirst(
                        scheduler.Schedule(
                            (@this: this, source, observer),
                            (closureScheduler, state) =>
                            {

View on GitHub (pinned to 94b5d5ab91)