Quick answer: Use a CancellationTokenSource per node, cancel in _ExitTree, pass the token to async methods, and gate post-await access with IsInsideTree.

A C# enemy AI awaits a tween, then sets the next move. Player kills the enemy mid-await, queue_free fires, scene reloads. The await resumes against a disposed node — ObjectDisposedException at runtime.

Cancellation Token Pattern

public partial class Enemy : CharacterBody3D
{
    private CancellationTokenSource _cts = new();

    public override async void _Ready()
    {
        try
        {
            await RunAi(_cts.Token);
        }
        catch (OperationCanceledException) { // expected on exit }
    }

    public override void _ExitTree()
    {
        _cts.Cancel();
        _cts.Dispose();
    }
}

Single source of cancellation. ExitTree flips the flag; all in-flight awaits throw at next opportunity.

Await Loops with Token Checks

private async Task RunAi(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        ct.ThrowIfCancellationRequested();
        await ToSignal(GetTree().CreateTimer(1.0), "timeout");
        if (!IsInsideTree()) return;
        PickNextTarget();
    }
}

Three layers of defense: token check, IsInsideTree check, then act. Any path can early-return safely.

Avoid Fire-and-Forget

async void methods (like _Ready) silently swallow exceptions. Don’t kick off side tasks with async void; use async Task and store the task so you can await/cancel it.

Timer Await Caveat

SceneTreeTimer signals don’t propagate cancellation. Use Task.Delay(ms, ct) with a token for cancellable waits, or wrap ToSignal in a try/catch that respects the token:

await Task.WhenAny(
    ToSignal(GetTree().CreateTimer(1), "timeout").AsTask(),
    Task.Delay(Timeout.Infinite, ct)
);
ct.ThrowIfCancellationRequested();

Verifying

Kill enemies mid-action repeatedly. No ObjectDisposedException. Debug log shows clean cancellation messages. Scene reload doesn’t leak in-flight tasks (check Task count via diagnostic profiler).

“Async + scene tree = a contract. Make ExitTree the contract’s end.”

For multiplayer or save systems, cancellation tokens also let you abort long operations on disconnect/save-cancel. One pattern, many uses.