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.