Cysharp/UniTask · error · InvalidOperationException

continuation is already registered.

Error message

continuation is already registered.

What it means

Thrown by Error.ThrowWhenContinuationIsAlreadyRegistered when the continuation field on a UniTask source is already non-null. UniTask sources support exactly one continuation (single-awaiter semantics); a second registration indicates the same source was awaited from two concurrent paths. This is by design — UniTask is a zero-allocation single-shot awaitable, not a multi-cast Task.

Source

Thrown at src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/Error.cs:69

        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static T ThrowNotYetCompleted<T>()
        {
            throw new InvalidOperationException("Not yet completed.");
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void ThrowWhenContinuationIsAlreadyRegistered<T>(T continuationField)
          where T : class
        {
            if (continuationField != null) ThrowInvalidOperationExceptionCore("continuation is already registered.");
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        static void ThrowInvalidOperationExceptionCore(string message)
        {
            throw new InvalidOperationException(message);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void ThrowOperationCanceledException()
        {
            throw new OperationCanceledException();
        }
    }
}

View on GitHub (pinned to ceac8d6946)

Solutions

  1. Treat each UniTask source as single-use: one awaiter only. Create a fresh source for each consumer.
  2. If multiple consumers need notification, use a separate mechanism (e.g., channel, CancellationTokenSource, or UniTask.WhenAll on individual tasks).
  3. Audit shared fields holding UniTask sources and ensure they are not awaited concurrently.

Example fix

// before
var tcs = new UniTaskCompletionSource();
async UniTask A() => await tcs.Task;
async UniTask B() => await tcs.Task;
// A(); B();  // throws: continuation already registered

// after
// Create a separate source per consumer, or use WhenAll pattern
var tcs1 = new UniTaskCompletionSource();
var tcs2 = new UniTaskCompletionSource();
await UniTask.WhenAll(tcs1.Task, tcs2.Task);
Defensive patterns

Strategy: validation

Validate before calling

// Do not share UniTask sources across multiple awaiters.
// Each UniTaskCompletionSource is single-use:
// BAD:  shared field awaited by two callers
// GOOD: create a new source per awaiter

Prevention

When it happens

Trigger: Awaiting the same UniTaskManualSource or configured UniTask source from two different async methods simultaneously. Or calling the continuation-registration API twice on the same token.

Common situations: Storing a UniTaskCompletionSource in a shared field and awaiting it from multiple callers. A race in cancellation logic that registers a second continuation. Incorrectly pooling/reusing a task source across multiple awaits.

Related errors


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