dotnet/reactive · error · ArgumentNullException

observer

Error message

observer

What it means

The AsyncObserver.TakeUntil<TSource, TUntil>(observer) factory throws ArgumentNullException because the downstream observer argument was null. This observer-level factory creates the paired (source, until) observers wired through an AsyncGate; it requires a non-null downstream IAsyncObserver to forward emissions to. The parameter name 'observer' identifies the null argument.

Solutions

  1. Pass the actual downstream IAsyncObserver you intend to forward to (e.g. the one obtained from CreateAsyncObserver or another operator factory).
  2. Check the composition order — create the downstream observer before calling AsyncObserver.TakeUntil.
  3. If writing a custom operator, assert observer != null at the entry of your SubscribeSafeAsync implementation to fail fast with a clearer message.

Example fix

// before
var (srcObs, untilObs) = AsyncObserver.TakeUntil<int, object>(null);
// after
var downstream = AsyncObserver.Create<int>(onNextAsync: ...);
var (srcObs, untilObs) = AsyncObserver.TakeUntil<int, object>(downstream);
Defensive patterns

Strategy: validation

Validate before calling

if (observer == null) throw new InvalidOperationException("downstream observer must be created before TakeUntil");

Type guard

bool HasObserver<T>(IAsyncObserver<T>? o) => o is not null;

Try / catch

try { var pair = AsyncObserver.TakeUntil<int, object>(observer); } catch (ArgumentNullException ex) when (ex.ParamName == "observer") { /* re-create downstream observer and retry composition */ }

Prevention

When it happens

Trigger: Calling AsyncObserver.TakeUntil<TSource, TUntil>(null) when composing custom observer pipelines.

Common situations: Building custom operators where the downstream observer comes from an upstream subscription that returned null; mis-ordered composition where the observer is created after it is passed; test scaffolding that forgot to instantiate the observer.

Related errors


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

Appendix: source

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

                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
    {
        public static (IAsyncObserver<TSource>, IAsyncObserver<TUntil>) TakeUntil<TSource, TUntil>(IAsyncObserver<TSource> observer)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));

            var gate = new AsyncGate();

            return
                (
                    Create<TSource>(
                        async x =>
                        {
                            using (await gate.LockAsync().ConfigureAwait(false))
                            {
                                await observer.OnNextAsync(x).ConfigureAwait(false);
                            }
                        },
                        async ex =>
                        {
                            using (await gate.LockAsync().ConfigureAwait(false))
                            {
                                await observer.OnErrorAsync(ex).ConfigureAwait(false);

View on GitHub (pinned to 94b5d5ab91)