Cysharp/UniTask · error · Exception
Attempting to use an invalid operation handle
Error message
Attempting to use an invalid operation handle
What it means
Thrown by the ToUniTask/WithCancellation extension methods for Unity Addressables' AsyncOperationHandle<T>. Before wrapping the handle into a UniTask source, the code checks handle.IsValid(); if false the handle was already released, never initialized, or is a default handle. There is no valid operation to await so it throws immediately.
Source
Thrown at src/UniTask/Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs:288
#region AsyncOperationHandle_T
public static UniTask<T>.Awaiter GetAwaiter<T>(this AsyncOperationHandle<T> handle)
{
return ToUniTask(handle).GetAwaiter();
}
public static UniTask<T> WithCancellation<T>(this AsyncOperationHandle<T> handle, CancellationToken cancellationToken, bool cancelImmediately = false, bool autoReleaseWhenCanceled = false)
{
return ToUniTask(handle, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately, autoReleaseWhenCanceled: autoReleaseWhenCanceled);
}
public static UniTask<T> ToUniTask<T>(this AsyncOperationHandle<T> handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false, bool autoReleaseWhenCanceled = false)
{
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<T>(cancellationToken);
if (!handle.IsValid())
{
throw new Exception("Attempting to use an invalid operation handle");
}
if (handle.IsDone)
{
if (handle.Status == AsyncOperationStatus.Failed)
{
return UniTask.FromException<T>(handle.OperationException);
}
return UniTask.FromResult(handle.Result);
}
return new UniTask<T>(AsyncOperationHandleConfiguredSource<T>.Create(handle, timing, progress, cancellationToken, cancelImmediately, autoReleaseWhenCanceled, out var token), token);
}
sealed class AsyncOperationHandleConfiguredSource<T> : IUniTaskSource<T>, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleConfiguredSource<T>>
{
static TaskPool<AsyncOperationHandleConfiguredSource<T>> pool;
AsyncOperationHandleConfiguredSource<T> nextNode;View on GitHub (pinned to ceac8d6946)
Solutions
- Do not call Addressables.Release(handle) until you are completely done awaiting the handle.
- Check handle.IsValid() before calling ToUniTask/WithCancellation: if (!handle.IsValid()) return;
- Use a reference-counted asset manager that tracks handle lifecycle and prevents premature release.
- Ensure handles are loaded fresh per async operation rather than cached and reused after release.
Example fix
// before Addressables.Release(handle); await handle.WithCancellation(cancellationToken); // throws // after await handle.WithCancellation(cancellationToken); // ... use handle.Result ... Addressables.Release(handle); // release only after all awaits are done
Defensive patterns
Strategy: validation
Validate before calling
if (!handle.IsValid())
{
// handle was released or never loaded; do not call ToUniTask
return;
}
var result = await handle.ToUniTask(); Type guard
static bool IsValidHandle<T>(AsyncOperationHandle<T> handle) => handle.IsValid();
Try / catch
try
{
var result = await handle.ToUniTask(cancellationToken: ct);
}
catch (Exception ex) when (ex.Message.Contains("invalid operation handle"))
{
// Handle was released before await; reload the asset
handle = Addressables.LoadAssetAsync<T>(key);
} Prevention
- Never call Addressables.Release(handle) before all awaits on that handle are complete.
- Use a reference-counted handle manager that tracks active consumers.
- Load handles fresh per operation rather than caching and reusing after release.
When it happens
Trigger: Calling handle.ToUniTask() or handle.WithCancellation(token) after Addressables.Release(handle) was already called. Or passing a handle variable that was never assigned a valid load result (left at default). Or re-using a handle stored in a cache after it was released.
Common situations: Releasing an Addressables asset handle and then attempting to await it again in a different code path. A reference-counting bug where a handle is released prematurely. Storing handles in a dictionary and accessing after eviction/release.
Related errors
- Channel is already closed.
- Enumerator is already running, does not allow call GetAsyncE
- Not yet completed.
- continuation is already registered.
- Can not trigger itself in iterating.
AI-assisted analysis of Cysharp/UniTask@ceac8d6946 (2026-08-13).
Data as JSON: /api/errors/001a387ff680dfb4.
Report an issue: GitHub.