Cysharp/UniTask · error · InvalidOperationException

AsyncSubject is not completed yet

Error message

AsyncSubject is not completed yet

What it means

Thrown by the AsyncSubject<T>.Value getter (line 380) when isStopped is false. UniTask returns an internal AsyncSubject<T> from UniTask<T>.ToObservable() as the Rx bridge; like the Rx AsyncSubject it caches only the last value and publishes it on completion, so reading Value before the source task has finished is a contract violation (InvalidOperationException). The subject only becomes readable after Fire() calls OnNext + OnCompleted.

Source

Thrown at src/UniTask/Assets/Plugins/UniTask/Runtime/UniTaskObservableExtensions.cs:380

    }

    internal sealed class AsyncSubject<T> : IObservable<T>, IObserver<T>
    {
        object observerLock = new object();

        T lastValue;
        bool hasValue;
        bool isStopped;
        bool isDisposed;
        Exception lastError;
        IObserver<T> outObserver = EmptyObserver<T>.Instance;

        public T Value
        {
            get
            {
                ThrowIfDisposed();
                if (!isStopped) throw new InvalidOperationException("AsyncSubject is not completed yet");
                if (lastError != null) ExceptionDispatchInfo.Capture(lastError).Throw();
                return lastValue;
            }
        }

        public bool HasObservers
        {
            get
            {
                return !(outObserver is EmptyObserver<T>) && !isStopped && !isDisposed;
            }
        }

        public bool IsCompleted { get { return isStopped; } }

        public void OnCompleted()
        {
            IObserver<T> old;

View on GitHub (pinned to ceac8d6946)

Solutions

  1. Check subject.IsCompleted before reading .Value (the getter only returns once isStopped is true).
  2. Await the original UniTask<T> directly instead of reading the observable's Value.
  3. Use .Subscribe(observer) or .ToUniTask() to receive the value asynchronously rather than reading it eagerly.

Example fix

// before
var obs = myTask.ToObservable();
var v = ((AsyncSubject<int>)obs).Value; // throws: task not done

// after
var v = await myTask; // await the source directly
Defensive patterns

Strategy: validation

Validate before calling

// AsyncSubject<T>.IsCompleted maps to isStopped; check before reading Value
var subject = (AsyncSubject<int>)myTask.ToObservable();
if (subject.IsCompleted) {
    var v = subject.Value;
} else {
    // not ready; await the source instead
}

Try / catch

try {
    var v = subject.Value;
} catch (InvalidOperationException) {
    // AsyncSubject not completed yet; await the source instead
}

Prevention

When it happens

Trigger: Accessing the .Value property of the observable returned by someUniTask.ToObservable() before the underlying UniTask<T> has awaited to completion (i.e. isStopped still false). Happens when code casts the IObservable back to AsyncSubject<T> (or reaches the getter) and reads it synchronously while the producing task is still in flight.

Common situations: Polling a converted observable's value from a non-async/synchronous caller; a race where the producing task is slow and Value is read immediately after ToObservable() returns; mistaking the IObservable for an already-resolved value box.

Related errors


AI-assisted analysis of Cysharp/UniTask@ceac8d6946 (2026-08-13). Data as JSON: /api/errors/152b9126086ef592. Report an issue: GitHub.