dotnet/reactive · error · ArgumentNullException

observer

Error message

observer

What it means

The observer-side Delay extension throws ArgumentNullException because the downstream IAsyncObserver<TSource> is null. Observer operators compose onto an existing observer, and a null observer has nothing to forward delayed notifications to, so the argument is validated immediately. Note this overload already defaults the scheduler, so only the observer itself is at fault here.

Solutions

  1. Pass a real IAsyncObserver<TSource> — typically the observer handed to your SubscribeAsync lambda.
  2. If you wrap observers in a chain, verify the innermost observer is constructed before the Delay call.
  3. Check that any factory producing the observer is not returning null.

Example fix

// before
await AsyncObserver.Delay(observer, TimeSpan.FromSeconds(1)); // observer null here
// after
if (observer == null) throw new InvalidOperationException("observer not yet created");
await AsyncObserver.Delay(observer, TimeSpan.FromSeconds(1));
Defensive patterns

Strategy: validation

Validate before calling

if (observer is null) throw new InvalidOperationException("downstream observer not created before Delay");
var (delayed, disp) = await AsyncObserver.Delay(observer, dueTime);

Type guard

static bool CanDelay<TSource>(IAsyncObserver<TSource>? o) => o is not null;

Try / catch

try { var r = await AsyncObserver.Delay(observer, due); }
catch (ArgumentNullException ex) when (ex.ParamName == "observer") { /* log and rethrow with pipeline context */ throw; }

Prevention

When it happens

Trigger: Calling AsyncObserver.Delay<TSource>(null, dueTime) — e.g. inside a custom SubscribeAsync where the 'observer' parameter or a captured observer variable is null.

Common situations: Writing a custom operator whose Create callback receives an observer that was never assigned; tests passing null observers; refactors that removed observer construction upstream.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Delay.cs:61

                source,
                (dueTime, scheduler),
                static async (source, state, observer) =>
                {
                    var (sink, drain) = await AsyncObserver.Delay(observer, state.dueTime, state.scheduler).ConfigureAwait(false);

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

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

    public partial class AsyncObserver
    {
        public static Task<(IAsyncObserver<TSource>, IAsyncDisposable)> Delay<TSource>(this IAsyncObserver<TSource> observer, TimeSpan dueTime)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));

            return Delay(observer, dueTime, TaskPoolAsyncScheduler.Default);
        }

        public static async Task<(IAsyncObserver<TSource>, IAsyncDisposable)> Delay<TSource>(this IAsyncObserver<TSource> observer, TimeSpan dueTime, IAsyncScheduler scheduler)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (scheduler == null)
                throw new ArgumentNullException(nameof(scheduler));

            // TODO: Use stopwatch functionality.

            var start = scheduler.Now;

            var semaphore = new SemaphoreSlim(0);

            var gate = new AsyncGate();

View on GitHub (pinned to 94b5d5ab91)