dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'observable')

Error message

Value cannot be null. (Parameter 'observable')

What it means

ToTask<TResult> throws ArgumentNullException with parameter name 'observable' when the source IObservable is null. The extension method validates its receiver before creating the TaskCompletionSource and observer.

Solutions

  1. Ensure the observable is initialized before calling ToTask.
  2. Null-check or use the null-conditional operator (?.) with a fallback.
  3. Fix upstream factory/DI code that returns null instead of an empty observable (Observable.Empty<TResult>()).

Example fix

// before
var task = maybeObservable.ToTask();
// after
var task = (maybeObservable ?? Observable.Empty<TResult>()).ToTask();
Defensive patterns

Strategy: type-guard

Validate before calling

if (observable == null) return Task.FromResult(default(TResult)!);

Type guard

bool IsValidSource<TResult>(IObservable<TResult>? o) => o is not null;
// usage: if (IsValidSource(source)) { var t = source.ToTask(); }

Try / catch

try { task = source.ToTask(); }
catch (ArgumentNullException) { task = Task.FromCanceled<TResult>(CancellationToken.None); }

Prevention

When it happens

Trigger: Invoking source.ToTask() where the source reference is null (extension methods can be called on null receivers).

Common situations: Nullable fields/properties holding the observable that were never initialized; factory methods returning null; chaining pipelines where an earlier operator produced null.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Threading/Tasks/TaskObservableExtensions.cs:591

            }
        }

        /// <summary>
        /// Returns a task that will receive the last value or the exception produced by the observable sequence.
        /// </summary>
        /// <typeparam name="TResult">The type of the elements in the source sequence.</typeparam>
        /// <param name="observable">Observable sequence to convert to a task.</param>
        /// <param name="cancellationToken">Cancellation token that can be used to cancel the task, causing unsubscription from the observable sequence.</param>
        /// <param name="state">The state to use as the underlying task's <see cref="Task.AsyncState"/>.</param>
        /// <returns>A task that will receive the last element or the exception produced by the observable sequence.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="observable"/> is <c>null</c>.</exception>
#pragma warning disable CA1068 // (CancellationToken parameters must come last.) Would be a breaking change.
        public static Task<TResult> ToTask<TResult>(this IObservable<TResult> observable, CancellationToken cancellationToken, object? state)
#pragma warning restore CA1068
        {
            if (observable == null)
            {
                throw new ArgumentNullException(nameof(observable));
            }

            var tcs = new TaskCompletionSource<TResult>(state);

            var taskCompletionObserver = new ToTaskObserver<TResult>(tcs, cancellationToken);

            //
            // Subtle race condition: if the source completes before we reach the line below, the SingleAssigmentDisposable
            // will already have been disposed. Upon assignment, the disposable resource being set will be disposed on the
            // spot, which may throw an exception.
            //
            try
            {
                //
                // [OK] Use of unsafe Subscribe: we're catching the exception here to set the TaskCompletionSource.
                //
                // Notice we could use a safe subscription to route errors through OnError, but we still need the
                // exception handling logic here for the reason explained above. We cannot afford to throw here

View on GitHub (pinned to 94b5d5ab91)