Search code examples
c#unity-game-engineasynchronousasync-await

Async method vs UniTask return


I'm trying to learn how async programming works in Unity, using the UniTask library.

Let's say we have this code:

private async UniTask LoadGameAsync()
{
    //this should be the right way to await the scene loading
    await LoadSceneAsync("Level_01");
    Debug.Log("Level 01 finished loading.");
    
    //does this work?
    //I'm uncertain if this is correct because I await a non async method. But then again it returns a task ... No compiler error.
    await LoadScene("Level_02");
    Debug.Log("Level 02 finished loading.");
    
    //why all this?
    //because I want to load multiple scenes simultaneously and only when all are finished loading, hide a loading screen.
    //Does this work the way I intend to or are there pitfalls to avoid? If so, what's the correct way of implementing something like this?
    UniTask level03Task = LoadScene("Level_03");
    UniTask level03UITask = LoadScene("Level_03_UI");
    await UniTask.WhenAll(level03Task, level03UITask);
    Debug.Log("Level 03 and its UI finished loading, hide loading screen now.");
}

private async UniTask LoadSceneAsync(string sceneName)
{
    await SceneManager.LoadSceneAsync(sceneName).ToUniTask();
}

private UniTask LoadScene(string sceneName)
{
    return SceneManager.LoadSceneAsync(sceneName).ToUniTask();
}

The comments in the code basically summarize my question. Is it possible to return a UniTask from a method and then await it? It somehow feels wrong because the method is basically the same as the async one but ... not async.

If this doesn't work as intended, what would be the correct way to implement what I imagine?

Note that UniTask in comparison to Task is a struct, not a class. Not sure if this makes a difference when creating the tasks for later use.

Thank you!


Solution

  • I'm uncertain if this is correct because I await a non async method. But then again it returns a task ... No compiler error.

    You await no methods, but awaitable objects. So far you await no a method, but the object it returns. It could be Task, UniTask, or any other objects, that are compatible with await keyword.

    Under the hood async/await is reconstructed to a state machine with no async/await keywords, but only with calling methods of awaitable objects with some auxiliary objects for its creation or something. You can try to check it, and other syntax sugar in the SharpLab.

       //why all this?
       //because I want to load multiple scenes simultaneously and only when all are finished loading, hide a loading screen.
       //Does this work the way I intend to or are there pitfalls to avoid? If so, what's the correct way of implementing something like this?
       UniTask level03Task = LoadScene("Level_03");
       UniTask level03UITask = LoadScene("Level_03_UI");
       await UniTask.WhenAll(level03Task, level03UITask);
       Debug.Log("Level 03 and its UI finished loading, hide loading screen now.");
    

    Lets deal with this.

        UniTask level03Task = LoadScene("Level_03");
        UniTask level03UITask = LoadScene("Level_03_UI");
    

    These two lines will be executed immediately. They start scenes loading process and creates a handles(UniTasks), that can be used to work with this process.

        await UniTask.WhenAll(level03Task, level03UITask);
    

    This line uses two tasks to make the third one, that will be a handle for their both completion. And after this task is created, it is instantly awaited. So the execution of the method will continue only after the awaited task is completed. And it will be completed after two scene loading tasks are completed.

    So far, if you want, to display any loading UI, you have to show it before this line and hide after this line.

    Note that UniTask in comparison to Task is a struct, not a class. Not sure if this makes a difference when creating the tasks for later use.

    In general, UniTasks and Tasks have much much deeper differences besides struct and class types. In most cases, you won't notice any differences between them, while you are working with Unity API. Maybe the thing, I can mention, is that UniTasks, by default, are executed inside the Unity main loop, while Task are executed, by default, in the separate thread, that could lead to problems with accessing some Unity API.