Cysharp/UniTask · error · InvalidOperationException
Disposable is already set
Error message
Disposable is already set
What it means
Thrown by SingleAssignmentDisposable.Disposable setter when a non-null disposable is already assigned and a different non-null value is set. SingleAssignmentDisposable is designed to hold exactly one disposable; assigning a second one before Dispose is a programming error indicating a logic bug. The old disposable is not auto-disposed — the throw is intentional to surface the misuse.
Source
Thrown at src/UniTask/Assets/Plugins/UniTask/Runtime/UniTaskObservableExtensions.cs:341
bool alreadyDisposed;
lock (gate)
{
alreadyDisposed = disposed;
old = current;
if (!alreadyDisposed)
{
if (value == null) return;
current = value;
}
}
if (alreadyDisposed && value != null)
{
value.Dispose();
return;
}
if (old != null) throw new InvalidOperationException("Disposable is already set");
}
}
public void Dispose()
{
IDisposable old = null;
lock (gate)
{
if (!disposed)
{
disposed = true;
old = current;
current = null;
}
}
View on GitHub (pinned to ceac8d6946)
Solutions
- Use SerialAssignmentDisposable (or dispose the old one first) if you need to swap disposables
- Call Dispose() on the SingleAssignmentDisposable before assigning a new value
- Use a separate SingleAssignmentDisposable for each subscription
- Null-check and dispose: if (holder.Disposable != null) holder.Disposable.Dispose(); before reassigning — but prefer SerialDisposable for this pattern
Example fix
// before var sad = new SingleAssignmentDisposable(); sad.Disposable = obs1.Subscribe(handler); sad.Disposable = obs2.Subscribe(handler); // throws // after (use SerialDisposable for swapping) var sd = new SerialDisposable(); sd.Disposable = obs1.Subscribe(handler); sd.Disposable = obs2.Subscribe(handler); // disposes obs1, ok
Defensive patterns
Strategy: validation
Validate before calling
// Before assigning, check if already set
if (holder.Disposable != null)
{
holder.Disposable.Dispose(); // or use SerialDisposable instead
}
holder.Disposable = newSubscription; Prevention
- Use SerialDisposable instead of SingleAssignmentDisposable when you need to swap subscriptions
- Call Dispose() before reassigning if reusing a SingleAssignmentDisposable is truly needed
- Create a new SingleAssignmentDisposable for each subscription lifecycle
- Audit Rx subscription code for shared disposable holders during refactoring
When it happens
Trigger: Setting the Disposable property twice on the same SingleAssignmentDisposable instance before calling Dispose. For example, subscribing twice and assigning each subscription to the same SingleAssignmentDisposable.
Common situations: Rx/Observable interop where multiple subscriptions reuse the same disposable holder. Event handler registration loops that overwrite the disposable. Refactoring that accidentally merges two subscription lifecycles into one SingleAssignmentDisposable.
Related errors
- Can not trigger itself in iterating.
- handler
- Delay does not allow minus delayFrameCount. delayFrameCount:
- Delay does not allow minus delayTimeSpan. delayTimeSpan:
- The tasks argument contains no tasks.
AI-assisted analysis of Cysharp/UniTask@ceac8d6946 (2026-08-13).
Data as JSON: /api/errors/fb27adcc97297817.
Report an issue: GitHub.