Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity Coroutine yield return null EQUIVALENT with Task async await

What is the equivalent of yield return null; in Coroutine (that run each frame at Update) in an async method ?

The nearest I got to find is await Task.Delay(1);, but it DO NOT run every frame.

private IEnumerator RunEachFrame()
{
    while (true)
    {
        print("Run Each frame right before rendering");
        yield return null;
    }
}

async void DoNotRunEachFrame()
{
    while (true)
    {
        await Task.Delay(1); // What is the equivalent of yield return null here ?
    }
}
like image 648
Ambroise Rabier Avatar asked Dec 23 '17 13:12

Ambroise Rabier


2 Answers

There is currently no equivalent method for for yield return null .

I was going to say it's not possible since async can be called in another Thread other than the main Thread which can throw exceptions since you can't use Unity's API in another Thread but it looks like Unity fixed the Thread issue by implementing their own async context in Unity 5.6.0b5 and above.


It' still possible to do but you have to implement it yourself or use an existing API. The UnityAsync API can do this already. You can get it here. The NextUpdate function replaces the yield return null instruction.

Examples:

Your usual coroutine code:

private IEnumerator RunEachFrame()
{
    while (true)
    {
        print("Run Each frame right before rendering");
        yield return null;
    }
}

The equivalent async code:

using UnityAsync;
using System.Threading.Tasks;

public class UpdateLoop : AsyncBehaviour
{
    void Start()
    {
        RunEachFrame();
    }

    // IEnumerator replaced with async void
    async void RunEachFrame()
    {
        while(true)
        {
            print("Run Each frame right before rendering");
            //yield return null replaced with await NextUpdate()
            await NextUpdate();
        }
    }
}

Notice how the script inherits from AsyncBehaviour instead of MonoBehaviour.


If you really want to inherit from MonoBehaviour instead of AsyncBehaviour and still use this API, call the NextUpdate function directly as Await.NextUpdate().Here is a complete equivalent example:

using UnityAsync;
using System.Threading.Tasks;

public class UpdateLoop : MonoBehaviour
{
    async void Start()
    {
        await RunEachFrame();
    }

    async Task RunEachFrame()
    {
        while(true)
        {
            print("Run Each frame right before rendering");
            await Await.NextUpdate(); // equivalent of AsyncBehaviour's NextUpdate
        }
    }
}

Below are the complete supported wait functions:

  • NextUpdate
  • NextLateUpdate
  • NextFixedUpdate
  • Updates(int framesToWait)
  • LateUpdates(int framesToWait)
  • FixedUpdates(int stepsToWait)
  • Seconds(float secondsToWait)
  • SecondsUnscaled(float secondsToWait)
  • Until(Func<bool> condition)
  • While(Func<bool> condition)
  • Custom(CustomYieldInstruction instruction)
  • AsyncOp(AsyncOperation op)

All these can be found in the Await class just in-case they get renamed or removed.

If you ever run into issues with this API, see Unity's forum post dedicated to it and ask questions there.

like image 109
Programmer Avatar answered Sep 20 '22 10:09

Programmer


At least in Unity 2018 you can use await Task.Yield(). For example:

using System.Threading.Tasks;
using UnityEngine;

public class AsyncYieldTest : MonoBehaviour
{
    async void Start()
    {
        await Function();
    }
    
    async Task Function() {
        while (gameObject != null)
        {
            await Task.Yield();
            Debug.Log("Frame: " + Time.frameCount);
        }
    }  
}

will give you output:

Frame: 1
Frame: 2
Frame: 3
...

It seems that if the Debug.Log("Frame: " + Time.frameCount); line was before await Task.Yield();, it would run twice during the first frame. I'm not sure what's the reason for that.

With UniTask library's UniTask.NextFrame it's possible to get behaviour that matches yield return null fully so that you don't get 2 messages on the first frame with

using Cysharp.Threading.Tasks;
using UnityEngine;

public class AsyncYieldTest : MonoBehaviour
{
    async void Start()
    {
        await Function();
    }

    async UniTask Function() {
        while (gameObject != null)
        {
            // Debug.Log first like with yield return null
            Debug.Log("Frame: " + Time.frameCount);
            await UniTask.NextFrame();
        }
    }  
}
like image 20
Hemaolle Avatar answered Sep 20 '22 10:09

Hemaolle