7
\$\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\$
3
\$\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 comments).
  • 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)
  • 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\$
5
  • \$\begingroup\$ To be fair, you can make custom await versions of all the built-in WaitFor... methods to stay in-step with the engine - this blog post includes some examples. In light of the reasons you give, are there any situations where you would recommend async/await over coroutines, or would you suggest always using coroutines? \$\endgroup\$ – DMGregory Aug 18 '20 at 19:54
  • \$\begingroup\$ @DMGregory I am not spotting any examples in that blog post that are equivalent to WaitForFixedUpdate or WaitForEndOfFrame. In regards to your question - my thought is to use coroutines unless you have a good reason not to. "A good reason not to" might be one of the reasons in that blog, or because you're working with a library that makes heavy use of asynchronous code (e.g. Firebase), or because you've profiled and found that the overhead of coroutines is costing you more performance than you can spare... in the end the developer has to decide what constitutes a "good reason" \$\endgroup\$ – Kevin Aug 18 '20 at 21:14
  • \$\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 Jan 21 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 Jan 21 at 21:00
  • \$\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 Jan 22 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.deltaTime * 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
  • 2
    \$\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 Aug 18 '20 at 16:05
  • \$\begingroup\$ @DMGregory I disagree with Unity there. But an answer with that point of view would be a great addition to this question. \$\endgroup\$ – Evorlor Aug 18 '20 at 16:54
  • 3
    \$\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 Aug 18 '20 at 17:14
  • \$\begingroup\$ @DMGregory I definitely didn't cover all the differences, or even close. Just the basics. \$\endgroup\$ – Evorlor Aug 18 '20 at 17:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.