dotnet/reactive · error · ArgumentNullException

scheduler

Error message

scheduler

What it means

The TakeUntil(source, endTime, scheduler) overload throws ArgumentNullException because the scheduler argument was null. The operator needs an IAsyncScheduler to schedule the end-time timer; the library rejects a null scheduler immediately with the parameter name 'scheduler' in the message. Note this overload validates both source and scheduler, so either being null yields this exception with the corresponding name.

Solutions

  1. Pass an explicit scheduler such as TaskPoolAsyncScheduler.Default or a NewThreadScheduler instance.
  2. Use the simpler TakeUntil(source, endTime) overload, which defaults to an appropriate scheduler.
  3. If the scheduler comes from configuration, validate it is bound before building the pipeline.

Example fix

// before
var bounded = source.TakeUntil(endTime, null);
// after
var bounded = source.TakeUntil(endTime, TaskPoolAsyncScheduler.Default);
Defensive patterns

Strategy: validation

Validate before calling

if (scheduler == null) scheduler = TaskPoolAsyncScheduler.Default;

Type guard

bool HasScheduler(IAsyncScheduler? s) => s is not null;

Try / catch

try { var bounded = source.TakeUntil(endTime, scheduler); } catch (ArgumentNullException ex) when (ex.ParamName == "scheduler") { var bounded = source.TakeUntil(endTime); }

Prevention

When it happens

Trigger: Calling AsyncObservable.TakeUntil(source, endTime, null) with a null IAsyncScheduler.

Common situations: A scheduler resolved from DI/configuration that failed to bind; passing a variable of type IAsyncScheduler that was never assigned; test code where the mock scheduler factory returned null.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/TakeUntil.cs:65

            return CreateAsyncObservable<TSource>.From(
                source,
                endTime,
                static async (source, endTime, observer) =>
                {
                    var (sourceObserver, timer) = await AsyncObserver.TakeUntil(observer, endTime).ConfigureAwait(false);

                    var subscription = await source.SubscribeSafeAsync(sourceObserver).ConfigureAwait(false);

                    return StableCompositeAsyncDisposable.Create(subscription, timer);
                });
        }

        public static IAsyncObservable<TSource> TakeUntil<TSource>(this IAsyncObservable<TSource> source, DateTimeOffset endTime, IAsyncScheduler scheduler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (scheduler == null)
                throw new ArgumentNullException(nameof(scheduler));

            // REVIEW: May be easier to just use TakeUntil with a Timer parameter. Do we want TakeUntil on the observer?

            return CreateAsyncObservable<TSource>.From(
                source,
                (endTime, scheduler),
                static async (source, state, observer) =>
                {
                    var (sourceObserver, timer) = await AsyncObserver.TakeUntil(observer, state.endTime, state.scheduler).ConfigureAwait(false);

                    var subscription = await source.SubscribeSafeAsync(sourceObserver).ConfigureAwait(false);

                    return StableCompositeAsyncDisposable.Create(subscription, timer);
                });
        }
    }

    public partial class AsyncObserver

View on GitHub (pinned to 94b5d5ab91)