dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'continuation')

Error message

Value cannot be null. (Parameter 'continuation')

What it means

SchedulerOperation implements the awaitable pattern for scheduler operations; OnCompleted registers the continuation passed by the compiler-generated awaiter. It throws ArgumentNullException when continuation is null. The compiler normally always passes a valid continuation, so this typically indicates manual/incorrect awaiter usage.

Solutions

  1. Always pass a non-null Action continuation to OnCompleted
  2. Await the SchedulerOperation with normal await syntax instead of calling OnCompleted manually
  3. Fix custom awaiter code to forward the real continuation

Example fix

// before
operation.OnCompleted(null);
// after
operation.OnCompleted(() => Console.WriteLine("resumed"));
// or simply:
await operation;
Defensive patterns

Strategy: validation

Validate before calling

if (continuation != null) operation.OnCompleted(continuation);

Type guard

static bool CanRegister(Action c) => c != null;

Try / catch

try { operation.OnCompleted(continuation); }
catch (ArgumentNullException) { /* supply a real continuation or skip */ }

Prevention

When it happens

Trigger: Calling operation.OnCompleted(null) directly; implementing a custom awaiter that passes a null continuation; misuse of the awaitable from another language or generated code.

Common situations: Hand-rolled awaiter implementations; reflection-based code invoking OnCompleted; tests exercising the awaitable pattern manually.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/SchedulerOperation.cs:93

        /// <summary>
        /// Indicates whether the scheduler operation has completed. Returns false unless cancellation was already requested.
        /// </summary>
        public bool IsCompleted => _cancellationToken.IsCancellationRequested;

        /// <summary>
        /// Completes the scheduler operation, throwing an OperationCanceledException in case cancellation was requested.
        /// </summary>
        public void GetResult() => _cancellationToken.ThrowIfCancellationRequested();

        /// <summary>
        /// Registers the continuation with the scheduler operation.
        /// </summary>
        /// <param name="continuation">Continuation to be run on the originating scheduler.</param>
        public void OnCompleted(Action continuation)
        {
            if (continuation == null)
            {
                throw new ArgumentNullException(nameof(continuation));
            }

            if (_continuation != null)
            {
                throw new InvalidOperationException(Strings_Core.SCHEDULER_OPERATION_ALREADY_AWAITED);
            }

            if (_postBackToOriginalContext)
            {
                var ctx = SynchronizationContext.Current;
                if (ctx != null)
                {
                    var original = continuation;
                    continuation = () =>
                    {
                        //
                        // No need for OperationStarted and OperationCompleted calls here;
                        // this code is invoked through await support and will have a way

View on GitHub (pinned to 94b5d5ab91)