Quick answer: Godot 4 C# ResourceLoader.LoadThreadedRequest callback running on the worker thread? GDScript ResourceLoader handles thread marshalling; C# requires CallDeferred to reach main.
Async-loaded texture assigned to a TextureRect on the worker thread. Godot asserts because Node mutations must be main-thread.
Marshal via CallDeferred
this.CallDeferred(MethodName.OnLoaded, resource);Defers the callback to the main thread. Required for any Node-touching code.
Poll on main thread
Instead of a callback, poll LoadThreadedGetStatus in _Process. Result is delivered on main; no marshalling.
Use SignalAwaiter
For C# idiomatic code, await a signal that's emitted from a CallDeferred. Code reads as single-threaded; runtime is correctly threaded.
“GDScript hides threading bookkeeping. C# requires you to do it.”
Wrap async load in a single helper that returns a Task. Internal CallDeferred is invisible to the caller; threading concerns stop spreading.