If you've only ever worked with coroutines, switching to async/await can feel almost magical. The code is cleaner, easier to read, and behaves much like synchronous code.
However, there's an important difference: Tasks are a .NET feature. Coroutines are a Unity feature.
That distinction affects how they interact with Unity's object lifecycle, scene management, and exception handling.
These differences are responsible for many of the mysterious NullReferenceExceptions and "WTF it worked yesterday!!" bugs that developers encounter when first adopting async.
Tasks don't adhere to Unity object lifetimes
This is arguably the biggest gotcha.
Imagine you write a simple async method to open a door:
public async Task OpenDoor()
{
await Task.Delay(3000);
animator.SetTrigger("Open");
}
Everything works perfectly until the player changes scenes, or destroys the object...or exits play mode. Three seconds later, the task resumes anyway. Now animator no longer exists.
NullReferenceException
You may wonder:
"The GameObject was destroyed. Why is this code still running?"
Because Tasks have no clue what a GameObject is. They're managed entirely by the .NET runtime.



