Quick answer: After every await, check IsInsideTree() and GodotObject.IsInstanceValid(this). Use a CancellationToken cancelled in _ExitTree. Catch OperationCanceledException to exit cleanly.
Async download completes; you set a property on the owning node. Crash — the node was removed from the tree or queue_freed.
The Symptom
ObjectDisposedException or null property access after an await. Hard to reproduce because timing-dependent.
The Fix
using System.Threading;
using System.Threading.Tasks;
public partial class Loader : Node
{
private readonly CancellationTokenSource _cts = new();
public override async void _Ready()
{
try
{
var data = await DownloadAsync(_cts.Token);
if (!IsInsideTree() || !IsInstanceValid(this)) return;
GetNode<Label>("%Status").Text = data;
}
catch (OperationCanceledException) { }
}
public override void _ExitTree()
{
_cts.Cancel();
_cts.Dispose();
}
}
Cancel on exit; check liveness on resume; catch the cancellation.
Verifying
Spawn the loader. queue_free it before the download completes. Should log nothing or a benign “cancelled” — never crash.
“Token. Validate. Catch cancel. Awaits stay safe.”
Related Issues
For C# CancellationToken, see CancellationToken. For C# disposed wrapper, see disposed wrapper.
Validate after await. Crashes vanish.