Search Unity

Waiting frames in async method instead of coroutine

Discussion in 'Scripting' started by Udi-Beres, Nov 20, 2019.

  1. Udi-Beres

    Udi-Beres

    Joined:
    Jan 27, 2015
    Posts:
    14
    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?


    Code (CSharp):
    1.     private async void MoveTransformAsync()
    2.     {
    3.         while(true)
    4.         {
    5.             transform.Translate(Vector3.right * Speed * Time.deltaTime);
    6.             await WaitFrame();
    7.         }
    8.     }
    9.  
    10.     private async Task WaitFrame()
    11.     {
    12.         var currnet = Time.frameCount;
    13.  
    14.         while (currnet == Time.frameCount)
    15.         {
    16.             await Task.Yield();
    17.         }
    18.     }
     
    CaseyHofland likes this.
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    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.
     
  3. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
    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.
     
  4. marcozakaria

    marcozakaria

    Joined:
    Sep 17, 2017
    Posts:
    23
    you can use
    await UniTask.DelayFrame(1) or await UniTask.Yield()
    or await Task.Delay(150);
     
  5. stevphie123

    stevphie123

    Joined:
    Mar 24, 2021
    Posts:
    82
    Task.Yield() isn't the same as `yield return` or 1 frame in Unity
     
  6. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    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)
     
  7. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    Or simply jobs (without ECS). No need to stick to both.
    I use jobs regularly in normal MonoBehaviour-based projects (e.g. for WorldGen)
     
  8. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    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?
     
    SF_FrankvHoof likes this.
  9. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    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' :D.
    I'd do the management of that job in LateUpdate.
     
  10. chemicalcrux

    chemicalcrux

    Joined:
    Mar 16, 2017
    Posts:
    720
    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!
     
  11. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    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.
     
    ModLunar and chemicalcrux like this.
  12. KyryloKuzyk

    KyryloKuzyk

    Joined:
    Nov 4, 2013
    Posts:
    1,142
    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.
     
    MiguelCoK and daxiongmao like this.
  13. Elhimp

    Elhimp

    Joined:
    Jan 6, 2013
    Posts:
    75