egametang/ET · error · InvalidOperationException

TaskT_TransitionToFinal_AlreadyCompleted

Error message

TaskT_TransitionToFinal_AlreadyCompleted

What it means

Thrown by ETTask.SetResult() when the task's state is no longer Pending — i.e. it was already completed (Succeeded or Faulted). ETTask objects may be pooled/reused, so completing a task twice corrupts state and is rejected with InvalidOperationException. Usually indicates double-completion, reuse-after-recycle, or a race where two paths resolve the same task.

Source

Thrown at Packages/cn.etetet.core/Scripts/Core/Share/ETTask/ETTask.cs:212

                    this.Recycle();
                    break;
                case AwaiterStatus.Faulted:
                    ExceptionDispatchInfo c = this.callback as ExceptionDispatchInfo;
                    this.callback = null;
                    this.Recycle();
                    c?.Throw();
                    break;
                default:
                    throw new NotSupportedException("ETTask does not allow call GetResult directly when task not completed. Please use 'await'.");
            }
        }

        [DebuggerHidden]
        public void SetResult()
        {
            if (this.state != AwaiterStatus.Pending)
            {
                throw new InvalidOperationException("TaskT_TransitionToFinal_AlreadyCompleted");
            }

            this.state = AwaiterStatus.Succeeded;

            Action c = this.callback as Action;
            this.callback = null;
            c?.Invoke();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        [DebuggerHidden]
        public void SetException(Exception e)
        {
            if (this.state != AwaiterStatus.Pending)
            {
                throw new InvalidOperationException("TaskT_TransitionToFinal_AlreadyCompleted");
            }

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Guard completions: if (task.state == AwaiterStatus.Pending) task.SetResult();
  2. Ensure exactly one code path owns task completion; route cancellation through the token, not a second SetResult/SetException.
  3. Avoid handing the same ETTask instance to multiple resolvers; do not reuse pooled tasks before reset.

Example fix

// before
task.SetResult();
task.SetResult(); // throws TaskT_TransitionToFinal_AlreadyCompleted

// after
task.SetResult();
// ensure no second completion; if needed, check state first.
Defensive patterns

Strategy: validation

Validate before calling

// Complete at most once.
if (task.state == AwaiterStatus.Pending) task.SetResult();

Try / catch

try { task.SetResult(); } catch (InvalidOperationException ex) when (ex.Message.Contains("AlreadyCompleted")) { /* log duplicate completion; ignore */ }

Prevention

When it happens

Trigger: Calling SetResult twice on the same ETTask; resolving a task both via SetResult and SetException; a recycled/pooled task being completed by stale code; concurrent fibers resolving the same task.

Common situations: Object-pool reuse without resetting state; fire-and-forget code paths (.Coroutine()) that also complete the task; cancellation token firing after SetResult; bugs where an awaiter callback and the producer both complete the task.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/c8161ce3e6f64d31. Report an issue: GitHub.