Unity Coroutine yield return null EQUIVALENT with Task async await

Unity Coroutine yield return null EQUIVALENT with Task async await

在异步方法中,Coroutine 中的 yield return null;(更新时的每一帧 运行)等效于什么?

我找到的最近的是 await Task.Delay(1);,但并不是每一帧都 运行。

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 ?
    }
}

目前没有 yield return null 的等效方法。

我想说这是不可能的,因为异步可以在主 Thread 之外的另一个 Thread 中调用,主 Thread 可能会抛出异常,因为您不能使用 Unity 的 API在另一个线程中,但它 looks 像 Unity 通过在 Unity 5.6.0b5 及更高版本中实现自己的异步上下文来解决线程问题。


仍然可以做到,但您必须自己实施或使用现有的 API。 UnityAsync API 已经可以做到这一点。你可以得到它hereNextUpdate 函数替换了 yield return null 指令。

示例:

你常用的协程代码:

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

等效的异步代码:

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();
        }
    }
}

注意脚本如何继承自 AsyncBehaviour 而不是 MonoBehaviour


如果你真的想继承MonoBehaviour而不是AsyncBehaviour并且仍然使用这个API,直接调用NextUpdate函数作为Await.NextUpdate()。这是一个完整的等效示例:

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
        }
    }
}

以下是完整支持的等待函数:

  • 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)

所有这些都可以在 Await class 中找到,以防它们被重命名或删除。

如果您 运行 对此 API 有任何疑问,请参阅 Unity 的论坛 post 并在那里提问。

至少在 Unity 2018 中你可以使用 await Task.Yield()。例如:

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);
        }
    }  
}

会给你输出:

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

似乎如果 Debug.Log("Frame: " + Time.frameCount); 行在 await Task.Yield(); 之前,它会在第一帧中 运行 两次。我不确定这是什么原因。

使用 UniTask libraryUniTask.NextFrame 可以获得完全匹配 yield return null 的行为,这样您就不会在使用

的第一帧上收到 2 条消息
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();
        }
    }  
}

首先,定义一个创建异步任务并使用协程等待帧结束的函数。

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);
}

现在你可以愉快的在async函数中使用了

await WaitOneFrameAsync();