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

  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.