Quick answer: ToSignal(node, signalName) never completes if the source is freed before firing. Add a timeout via SceneTree.CreateTimer().Timeout race or check IsInstanceValid before continuing the await chain.

Here is how to fix Godot 4 C# async/await on signals that hang forever when the source disappears. The SignalAwaiter has no built-in cancellation; you provide it via a timer race.

The Symptom

You await ToSignal(player, “FinishedAttack”) inside an enemy AI. Player is freed mid-attack. Await never returns.

What Causes This

Source freed. No signal fires; awaiter sits forever.

No cancellation primitive. SignalAwaiter does not respect CancellationToken.

The Fix

Step 1: Race against a timeout timer.

public async Task<bool> WaitForSignalOrTimeout(GodotObject src, string sig, double timeoutSec)
{
    Task signal = src.ToSignal(src, sig);
    Task timer = ToSignal(GetTree().CreateTimer(timeoutSec), Timer.SignalName.Timeout);
    Task winner = await Task.WhenAny(signal, timer);
    return winner == signal;
}

WhenAny returns whichever completes first.

Step 2: Validate before continuing.

await WaitForSignalOrTimeout(player, "FinishedAttack", 5.0);
if (!IsInstanceValid(this) || !IsInstanceValid(player))
    return;

Step 3: For repeated awaits, build a helper.

public async Task WaitFrames(int count)
{
    for (int i = 0; i < count; i++)
        await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}

Idiomatic frame-based await for animations.

Step 4: Avoid awaiting on non-Node objects. ToSignal on Resources or freed objects is unstable. Stick to Node sources.

Step 5: For long-running work, use Task.Run carefully. Task.Run leaves the main thread; returning to update Godot nodes requires await on a SceneTree-bound task.

“Race ToSignal vs Timer.Timeout. Validate before resuming. Frame-await helpers for animation flow.”

Related Issues

For C# disposed errors, see GodotObject Disposed. For C# signal disconnect, see Signal Disconnect.

Timer race for timeout. IsInstanceValid checks. Frame-await helpers. No hangs.