Quick answer: Godot 4 C# TaskCompletionSource<T> await resuming on a background thread? Default thread continuation - set RunContinuationsAsynchronously and route through Godot's sync context.

Custom async API uses TCS. Continuation runs on background; Node touches throw.

RunContinuationsAsynchronously

new TaskCompletionSource<T>(
  TaskCreationOptions.RunContinuationsAsynchronously)

Continuations queued; don't run on the SetResult thread.

Route through CallDeferred

Inside the continuation, CallDeferred to reach main. Explicit thread bridging.

Or capture context

Capture SynchronizationContext.Current at await time; post continuation to it. Cleaner if context is installed.

“TCS continuations have default thread behavior. The default isn't main.”

Build a GodotTaskCompletionSource wrapper that handles context. Misuse-resistant; reusable.

Related reading