Unity Coroutine yield return null EQUIVALENT with Task async await
Asked Answered
E

5

11

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 ?
    }
}
Eligibility answered 23/12, 2017 at 13:38 Comment(2)
No. You can only use Unity's API in the main thread. Maybe there is a workaround if you explain what you are doingIguanodon
I am running them on the main thread: Unity does provide one important piece for us however. As you can see in the above example, our async methods will be run on the main unity thread by default. In non-unity C# applications, async methods are often automatically run on separate threads stevevermeulen.com/index.php/2017/09/…Eligibility
I
10

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.

Iguanodon answered 23/12, 2017 at 22:5 Comment(2)
This is github.com/Cysharp/UniTask a better alternative to the UnityAsync API.Circle
@Circle UniTask doesn't have some equivalents, for example WaitForEndOfFrame doesn't work for ScreenCapture.CaptureScreenshotAsTexture(). github.com/Cysharp/UniTask#playerloopEagleeyed
J
13

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();
        }
    }  
}
Jordans answered 21/1, 2020 at 7:38 Comment(0)
I
10

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.

Iguanodon answered 23/12, 2017 at 22:5 Comment(2)
This is github.com/Cysharp/UniTask a better alternative to the UnityAsync API.Circle
@Circle UniTask doesn't have some equivalents, for example WaitForEndOfFrame doesn't work for ScreenCapture.CaptureScreenshotAsTexture(). github.com/Cysharp/UniTask#playerloopEagleeyed
O
0

First, define a function that creates async task and uses a coroutine to wait for end of the frame.

Task<bool> WaitOneFrameAsync()
{
    var tcs = new TaskCompletionSource<bool>();
    StartCoroutine(WaitOneFrame(tcs));
    return tcs.Task;
}

static IEnumerator WaitOneFrame(TaskCompletionSource<bool> tcs)
{
    yield return new WaitForEndOfFrame();
    tcs.TrySetResult(true);
}

Now you can happily use it in async function

await WaitOneFrameAsync(); 
Overliberal answered 26/2, 2021 at 8:53 Comment(0)
G
0

you can use one of these solutions:

1.

await UniTask.DelayFrame(1)
  1. await UniTask.Yield()
    
  2. await Task.Delay(150);
    
Geyer answered 23/7, 2022 at 13:7 Comment(1)
Please read How do I write a good answer?. While this code block may answer the OP's question, this answer would be much more useful if you explain how this code is different from the code in the question, what you've changed, why you've changed it and why that solves the problem without introducing others.Eimile
K
0

I got this way:

private async void NextFrameCall()
{
    int currentFrame = Time.renderedFrameCount;
    while (currentFrame >= Time.renderedFrameCount)
        await Task.Yield();
        
    //NextFrameMethodHere();
}
Kashgar answered 14/12, 2022 at 1:5 Comment(1)
Btw, on the example above I used the next RENDERED FRAME, if you want the NEXT FRAME use "Time.frameCount".Kashgar

© 2022 - 2024 — McMap. All rights reserved.