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.

Solutions

  1. Do not call Addressables.Release(handle) until you are completely done awaiting the handle.
  2. Check handle.IsValid() before calling ToUniTask/WithCancellation: if (!handle.IsValid()) return;
  3. Use a reference-counted asset manager that tracks handle lifecycle and prevents premature release.
  4. 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

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


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

Appendix: 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)