dotnet/reactive · error · InvalidOperationException

The asynchronous operation completed unexpectedly.

Error message

The asynchronous operation completed unexpectedly.

What it means

In the bridge's Done callback, this InvalidOperationException is thrown when a continuation callback observes the IAsyncInfo in a status other than Error, Canceled, or Completed after the initial phase — i.e. the operation finished with an unrecognized/invalid state. It signals a contract violation by the underlying IAsyncInfo implementation.

Solutions

  1. Fix the IAsyncInfo implementation so its Status only ever reports Error, Canceled, or Completed
  2. Wrap the observable subscription in try-catch handling InvalidOperationException
  3. Report the invalid status transitions to the vendor of the async component

Example fix

// before: rogue implementation returns default status after completion
// after: guarantee terminal status
Status => _completed ? (_canceled ? AsyncStatus.Canceled : _exception != null ? AsyncStatus.Error : AsyncStatus.Completed) : AsyncStatus.Started
Defensive patterns

Strategy: try-catch

Validate before calling

var s = asyncInfo.Status; if (s != AsyncStatus.Started && s != AsyncStatus.Error && s != AsyncStatus.Canceled && s != AsyncStatus.Completed) throw new InvalidOperationException($"Invalid async status: {s}");

Type guard

static bool HasValidTerminalStatus(AsyncStatus s) => s == AsyncStatus.Error || s == AsyncStatus.Canceled || s == AsyncStatus.Completed;

Try / catch

try { observable.ToTask(); } catch (InvalidOperationException ex) when (ex.Message == "The asynchronous operation completed unexpectedly.") { /* invalid state */ }

Prevention

When it happens

Trigger: A resumption of the Done callback (initial=false) where the IAsyncInfo status is not one of the three valid terminal statuses — typically a rogue implementation whose Status getter returns an unexpected value after firing the completion event.

Common situations: Buggy custom WinRT async implementations, incorrect manual IAsyncInfo implementations in C++/WinRT or projections, or state races in non-conformant async objects.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Platforms/WinRT/Foundation/AsyncInfoToObservableBridge.cs:64

            switch (status)
            {
                case AsyncStatus.Error:
                    error = info.ErrorCode ?? throw new InvalidOperationException("The asynchronous operation failed with a null error code.");
                    break;
                case AsyncStatus.Canceled:
                    error = new OperationCanceledException();
                    break;
                case AsyncStatus.Completed:
                    if (_getResult != null)
                    {
                        result = _getResult(info);
                    }

                    break;
                default:
                    if (!initial)
                    {
                        throw new InvalidOperationException("The asynchronous operation completed unexpectedly.");
                    }

                    _onCompleted(info, (iai, s) => Done(iai, s, false));
                    return;
            }

            //
            // Close as early as possible, before running continuations which could fail. In case of
            // failure above, we don't close out the object in order to allow for debugging of the
            // rogue implementation without losing state prematurely. Notice _getResult is merely
            // an indirect call to the appropriate GetResults method, which is not supposed to throw.
            // Instead, an Error status should be returned.
            //
            info.Close();

            //
            // Now we run the continuations, which could take a long time. Failure here is catastrophic
            // and under control of the upstream subscriber.

View on GitHub (pinned to 94b5d5ab91)