Waiting frames in async method instead of coroutine

Hi

What do you think about the next piece of code?
Can it replace a coroutine that do the same thing with yield return null?

    private async void MoveTransformAsync()
    {
        while(true)
        {
            transform.Translate(Vector3.right * Speed * Time.deltaTime);
            await WaitFrame();
        }
    }

    private async Task WaitFrame()
    {
        var currnet = Time.frameCount;

        while (currnet == Time.frameCount)
        {
            await Task.Yield();
        }
    }
1 Like

I don’t believe there’s any guarantee that your method would get to continue on the next Unity frame; theoretically, multiple frames could elapse before it gets a chance to check. (Once you call Task.Yield(), the system gets to decide how to prioritize your function relative to other stuff it’s trying to do, and there’s no guarantee it will get back to you promptly.)

Also, a lot of UnityEngine is not threadsafe. I’m not sure whether it’s OK to call Transform.Translate or access Time.frameCount from an async method. (I’m not sure it isn’t safe, but I’m not sure it is, either.) It might even appear to work in some test cases but then give weird errors in others.

If you want to do something every frame, it’s probably smarter to put it in Update.

If your performance requirements really require you to update transforms in tasks (like a there’s 10000x10000 armies clash in one scene) i suggest you to look into ecs+jobs.

you can use
await UniTask.DelayFrame(1) or await UniTask.Yield()
or await Task.Delay(150);

3 Likes

Task.Yield() isn’t the same as yield return or 1 frame in Unity

Yes. Just remember that Coroutines run on the main Unity-Thread, and tasks don’t.
So you can start running into issues when you try to use ‘Unity-Things’ inside your task.
That can be as quick as using a ‘.gameObject’ or a ‘GetComponent’ (or even Debug.Log in some instances)

Or simply jobs (without ECS). No need to stick to both.
I use jobs regularly in normal MonoBehaviour-based projects (e.g. for WorldGen)

Even synchronous jobs can be very useful! I use Bursted Jobs to do expensive AI calculations for a game, using a gradient descent algorithm. It was a huge speedup.

…I really should make those async, because the AI can afford to make one-frame-out-of-date decisions, can’t it?

1 Like

Yes. Running your jobs whilst the engine is doing rendering of the frame allows you to dump a bunch of work in a timeframe that’s normally somewhat ‘unused’ :smile:.
I’d do the management of that job in LateUpdate.

Ah, that’s a good idea.

I wrote that code before I really learned the Jobs system (while trying out ECS). It’s all very neat stuff!

Yeah I had to learn it a few years ago (back when none of it was documented, or even stable) in order to position & render a bunch of Stars (based on real-world data).
Went from +/- 400 GameObjects struggling to update once per second (split out over different frames) to 10k+ stars recalculating their position from scratch (Basically a bunch of trigonometry) once every 4 frames at <2ms total per frame
Sh*t’s crazy when you see performance-gains like that.

2 Likes

Unity 2023 introduced a new Awaitable.NextFrameAsync API that can wait for the next frame. Haven’t tried it myself, but Tarodev recently released a great video on this topic.

5 Likes

It’s third party tool, sure. But if you’re heavy on async it’s better to have it right.

Use UniTask package. They have everything needed like await UniTask.NextFrame() and it is totally okay to use it exclusively in your async code instead of standard Task because UniTask was designed for no-garbage.

Thanks! I’ve known about UniTask from the beginning, but it doesn’t answering my question :slight_smile:

AWSOME!

I should check how it works behind the scenes

By the way, UniTask is great, but for simpler tasks, I prefer not to import large packages into my project.