11
\$\begingroup\$

Should I use Async & Await in Unity? Or should I keep using Coroutines? If I should use Async & Await, how can I do so in a manner comparable to Coroutines?

\$\endgroup\$

2 Answers 2

10
\$\begingroup\$

In contrast to Evorlor's answer, here are some good reasons to use Coroutines:

  • They've been the standard solution in Unity for many years and anyone else you bring onto the team should already be comfortable with them.
  • They are in step with the engine. WaitForFixedUpdate() ensures that the next step in the Coroutine will be run during the Fixed Update cycle. WaitForEndOfFrame() ensures that the next step will be run in the Update cycle. If you use C#'s async/await, there's no way to ensure it's complicated to ensure that the code remains in-step with the engine (see below).
  • Likewise, WaitForSeconds() takes the current Time.timeScale into consideration, something that's not possible with await.
  • Coroutines are automatically stopped when the object that is running them is destroyed or disabled (this is occasionally undesirable, in which case you might want async/await).

When should I not use coroutines?

  • When you want a coroutine-like function to return a value (see this blog post recommended by DMGregory for how to configure these to work with the main thread when needed)
  • If you are having trouble getting error handling to work with your coroutine (see blog post from previous bullet. Note that you can put a try-catch block in a coroutine, as long as that block doesn't include a "yield" instruction).
  • If you've profiled your application and found that coroutines are costing you performance overhead that you can't spare, and this can't be fixed by optimizing your coroutines (unlikely for most games)
  • If you need to run a coroutine-like function from a regular C# class that doesn't extend MonoBehaviour
  • If you need a coroutine-like function to keep runnning when its GameObject is destroyed or disabled.
  • When working with a library that makes heavy use of async (e.g. Google Firebase)
  • When your code needs to be parallel (multi-threaded). Although they can be used to improve performance by distributing an expensive operation over multiple frames, Coroutines are not threaded. When you need multi-threaded code for maximum performance, use C# Threads or the Unity Job System.
  • If you have any other good reason not to use coroutines

In my experience, all of these exceptions are very rare and you don't normally need to consider them. YMMV depending on the project and your coding style.

\$\endgroup\$
3
  • \$\begingroup\$ responding to these two bullets: "If you need to run a coroutine-like function from a regular C# class... If you need a coroutine-like function to keep runnning..." Note that I frequently start coroutines on some other object to get around both these limitations. You can call otherObject.StartCoroutine(MyMethod()) \$\endgroup\$
    – jhocking
    Commented Jan 21, 2021 at 19:45
  • \$\begingroup\$ @jhocking Yes, but that can be conceptually confusing, and there's a risk of accidentally killing the coroutine by deactivating or destroying the object that's running it. The more developers on the team, the more likely this trick will cause confusion or bugs. And issues caused by coroutines stopping prematurely are often particularly difficult to diagnose. \$\endgroup\$
    – Kevin
    Commented Jan 21, 2021 at 21:00
  • 1
    \$\begingroup\$ Although I've never found it necessary, I sometimes contemplate creating a global CoroutineManager object for the very reasons you point out. That is, there's a shared object specifically designated as one to launch coroutines on that is guaranteed to be good for that purpose. \$\endgroup\$
    – jhocking
    Commented Jan 22, 2021 at 14:57
1
\$\begingroup\$

Should I use Async & Await or Coroutines?

You should use Async & Await. Coroutines were Unity's solution to running multiple simultaneous processes at a time when the version of C# it supported did not have Async & Await. Now that Unity's version of C# supports Async & Await, Coroutines are obsolete.

What is the difference between them?

Coroutines check back every frame to see if the condition has been met. Async & Await set a timer, and come back when the time is up. Async & Await is more efficient, but negligibly so. Async & Await can have return values much more easily than Coroutines.

How can I use Async & Await in place of Coroutines?

These two code snippets behave the same way:

public IEnumerator CoroutineMethod()
{
    //Exits method early
    if(ShouldEarlyExit())
    {
        Debug.Log("Early exiting.");
        yield break;
    }

    //Wait until condition has been met
    yield return new WaitUntil(() => ConditionMet());
    Debug.Log("Condition has been met.");
    //or
    while (!ConditionMet())
    {
        yield return new WaitForEndOfFrame();
        Debug.Log("Condition has been met.");
    }

    //Run code every n seconds
    while (true)
    {
        yield return new WaitForSeconds(1.0f);
        Debug.Log("waiting for 1 second.");
    }
}

public async void AsyncAwaitMethod()
{
    //Exits method early
    if (ShouldEarlyExit())
    {
        Debug.Log("Early exiting.");
        return;
    }

    //Wait until condition has been met
    while(!ConditionMet())
    {
        await Task.Delay(Mathf.CeilToInt(Time.maxDeltaTime * 1000));
        Debug.Log("Condition has been met.");
    }

    //Run code every n seconds
    while (true)
    {
        await Task.Delay(1000);
        Debug.Log("waiting for 1 second.");
    }
}
\$\endgroup\$
4
  • 3
    \$\begingroup\$ This answer seems to be at odds with Unity's own recommendations on when to use Coroutines vs Async/Await. Specifically they recommend using async/await for IO (including user input and network calls), and continuing to use coroutines for fire-and-forget behaviours. \$\endgroup\$
    – DMGregory
    Commented Aug 18, 2020 at 16:05
  • 6
    \$\begingroup\$ Note that the two pieces of code you've shown do not behave the same in a few important ways. 1) If the MonoBehaviour running the coroutine is destroyed/deactivated, the coroutine stops, but the async method continues looping forever. 2) The coroutine respects changes to Time.timeScale, whereas the async method ignores this. (The coroutine can be made to use unscaled time with WaitForSecondsRealtime if desired) 3) A delay proportional to deltaTime as shown here does not guarantee the async method resumes at a particular phase of the game loop. \$\endgroup\$
    – DMGregory
    Commented Aug 18, 2020 at 17:14
  • \$\begingroup\$ @DMGregory I definitely didn't cover all the differences, or even close. Just the basics. \$\endgroup\$
    – Evorlor
    Commented Aug 18, 2020 at 17:16
  • 1
    \$\begingroup\$ In my experience, coroutines remain useful, so I think calling them 'obsolete' is misleading. \$\endgroup\$
    – Xtro
    Commented Feb 22, 2022 at 19:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .