Task.Yield() / Task.Delay(0) hang

using System.Threading.Tasks;

using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        TestAsync();
    }

    async void TestAsync()
    {
        int i = 0;
        while (true)
        {
            Debug.Log($"### TestAsync: {++i}");
            await Task.Delay(1); // ok
            // await Task.Delay(0); // hang!
            // await Task.Yield(); // hang!
        }
    }
}

While posting this thread, I do notice that there is another thread talking about a similar issue: Help with async await

But regardless, the above code when attached to a scene will hang the whole Unity editor when using Task.Delay(0) and Task.Yield(), is this the expected behavior?

I’ve filed a bug for this, will update this post with the issuetracker asap

I’ve looked at this issue and found that Task.Delay(0) runs synchronously. You can find details about this behavior on stackoverflow c# - Not using await Task.Delay(x) (x>0) makes UI thread freeze - Stack Overflow

We have made one change due to this report however. Currently, when processing async/await continuations, we currently process the continuations that are spawned from existing continuations. This is why Task.Yield() would hang. Instead soon (once the changes land in 2018.1), if an ā€œawaitedā€ function calls await again, that continuation will be processed on to the next pass through the player loop. I should mention that we’re also considering changes to where we process continuations in the playerloop (currently fixedupdate) since this behavior seems inconsistent in some ways with how other .NET implementations work.

Did the process continuation move from Fixed Update ? I can’t find any documentation on it. It’d be great to see that in the player loop here: https://docs.unity3d.com/Manual/ExecutionOrder.html

I stumbled across this and did some digging (Unity 2019 LTS). It seems it moved to Update (evaluated once per frame). It is called between Update() and LateUpdate() and after coroutine ā€œyield nullā€.

Here is how the updated lifecycle graph would look like:

One caveat: they may be processed after the ā€œInternal animation updateā€, I have not tested that.
Test was done with a loop like this:

while (predicate() == false)
{
    await Task.Yield();
}

If you want to be extra sure it’s done only once per frame you may use something like this:

// Thanks to https://github.com/modesttree/Unity3dAsyncAwaitUtil
// License MIT: https://github.com/modesttree/Unity3dAsyncAwaitUtil/blob/master/License.md
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public static class Awaiters
{
    readonly static WaitForUpdate _waitForUpdate = new WaitForUpdate();

    public static WaitForUpdate NextFrame
    {
        get { return _waitForUpdate; }
    }

    public static async Task Until(CancellationToken ct, Func<bool> predicate)
    {
        while(predicate() == false && ct.IsCancellationRequested == false)
        {
            await NextFrame;
        }
    }

    public static async Task While(CancellationToken ct, Func<bool> predicate)
    {
        while (predicate() == true && ct.IsCancellationRequested == false)
        {
            await NextFrame;
        }
    }
}

Would be nice to get some confirmation for this.

I am facing the new bug in Unity2021.3.0f1:
When I use await Task.Yield() in Editor, everything works fine, but after I build the game, it just ā€˜freeze’ on this line of code. Here’s the test code:

    void Start()
    {
        Test();
    }
    async void Test()
    {
        Debug.LogError("Test Begin");
        await Task.Yield();
        Debug.LogError("Test End");//Will not be executed!
    }

Are you using IL2CPP? If yes, then it doesn’t work.

Thanks for your reply, I am using the good old Mono.

For async/await use this: GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.

1 Like