dotnet/reactive · error · ArgumentNullException

null (Parameter 'scheduler')

Error message

null (Parameter 'scheduler')

What it means

The Append operator's disposer-returning overload (IAsyncObserver, value, IAsyncScheduler) requires a non-null scheduler because appended values are emitted on that scheduler. System.Reactive.Async guards its public API surface eagerly, throwing ArgumentNullException with C# 10's raw parameter-name message format ("null (Parameter 'scheduler')") at the operator factory rather than failing later inside the async pipeline. Passing null means the operator could never schedule the appended value, so it fails fast.

Solutions

  1. Pass a concrete IAsyncScheduler such as AsyncScheduler.Default (or AsyncScheduler.Immediate / a task-pool scheduler) at the call site.
  2. If the scheduler comes from a caller or DI container, register/initialize it before constructing the Append operator.
  3. Add a null check with a default at the call boundary: scheduler ??= AsyncScheduler.Default.

Example fix

// before
var (obs, disp) = AsyncObservable.Append(observer, 42, null);
// after
var (obs, disp) = AsyncObservable.Append(observer, 42, AsyncScheduler.Default);
Defensive patterns

Strategy: validation

Validate before calling

if (scheduler is null) throw new InvalidOperationException("Append requires a scheduler; pass AsyncScheduler.Default.");
var (obs, disp) = AsyncObservable.Append(observer, value, scheduler);

Type guard

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

Try / catch

try { var (obs, disp) = AsyncObservable.Append(observer, value, scheduler); }
catch (ArgumentNullException ex) when (ex.ParamName == "scheduler") { scheduler = AsyncScheduler.Default; /* retry */ }

Prevention

When it happens

Trigger: Calling AsyncObservable.Append(observer, value, scheduler) with scheduler == null, e.g. Append(observer, value, null) or a variable assigned from a lookup that returned null.

Common situations: Resolving the scheduler from a DI container or config that was not registered; caching a scheduler in a field whose initialization was skipped; migrating code that previously used a synchronous overload and passing null where a scheduler is now required.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Append.cs:156

                throw new ArgumentNullException(nameof(observer));

            return Create<TSource>(
                observer.OnNextAsync,
                observer.OnErrorAsync,
                async () =>
                {
                    await observer.OnNextAsync(value).ConfigureAwait(false);
                    await observer.OnCompletedAsync().ConfigureAwait(false);
                }
            );
        }

        public static (IAsyncObserver<TSource>, IAsyncDisposable) Append<TSource>(IAsyncObserver<TSource> observer, TSource value, IAsyncScheduler scheduler)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (scheduler == null)
                throw new ArgumentNullException(nameof(scheduler));

            var d = new SingleAssignmentAsyncDisposable();

            return
                (
                    Create<TSource>(
                        observer.OnNextAsync,
                        observer.OnErrorAsync,
                        async () =>
                        {
                            var task = await scheduler.ScheduleAsync(async ct =>
                            {
                                if (!ct.IsCancellationRequested)
                                {
                                    await observer.OnNextAsync(value).RendezVous(scheduler, ct);
                                    await observer.OnCompletedAsync().RendezVous(scheduler, ct);
                                }
                            }).ConfigureAwait(false);

View on GitHub (pinned to 94b5d5ab91)