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
- Treat each UniTask source as single-use: one awaiter only. Create a fresh source for each consumer.
- If multiple consumers need notification, use a separate mechanism (e.g., channel, CancellationTokenSource, or UniTask.WhenAll on individual tasks).
- 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
- Treat every UniTask/UniTaskCompletionSource as single-awaiter — never store one in a shared field that multiple async methods await.
- For multi-consumer notification, use channels or multiple independent sources.
- Do not reuse or pool UniTask sources across awaits.
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
- Channel is already closed.
- Enumerator is already running, does not allow call GetAsyncE
- Attempting to use an invalid operation handle
- Not yet completed.
- AsyncSubject is not completed yet
AI-assisted analysis of Cysharp/UniTask@ceac8d6946 (2026-08-13).
Data as JSON: /api/errors/d3b914c4d0c88328.
Report an issue: GitHub.