Quick answer: Check GodotObject.IsInstanceValid(this) after every await. Cancel via CancellationToken on _ExitTree.

Coroutine awaits a 3-second timer. During the wait, the node is freed. Resume crashes with ObjectDisposedException.

The Fix

private CancellationTokenSource _cts = new();

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

public async Task DelayedAction() {
    try {
        await Task.Delay(3000, _cts.Token);
    } catch (OperationCanceledException) { return; }

    if (!GodotObject.IsInstanceValid(this)) return;
    GD.Print("safe to use this");
}

Cancellation cleanly stops the wait. IsInstanceValid handles the corner where Godot frees without dispose call.

Verifying

Free node mid-coroutine. No exception. With cancellation: returns immediately.

“Cancel on exit. Validate after await.”

Related Issues

For C# async deadlock, see async deadlock. For tween freed object, see tween freed.

Validate after await. Cancel on exit.