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
- Fix the IAsyncInfo implementation so its Status only ever reports Error, Canceled, or Completed
- Wrap the observable subscription in try-catch handling InvalidOperationException
- 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
- Prefer standard async primitives (Task.ToObservable, AsyncInfo.Run) over custom IAsyncInfo
- Ensure completion events fire only in terminal states
- Add state-machine unit tests for custom async objects
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
- The asynchronous operation failed with a null error code.
- The asynchronous operation failed with a null error code.
- The asynchronous operation completed unexpectedly.
- source (Parameter 'source')
- source
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)