Help with async await

Can someone give me a brief overview of how to best use async/await with Unity? I’m still having trouble wrapping my brain around it. How do we access the Unity SynchronizationContext? How would one replace a Coroutine with an async method? How do we tell an async method to run on the main thread vs a background thread? Is there an equivalent of “yield return null” that you can call from an async method in order to wait until the next Update frame?

2 Likes

I’m also interested in this.

I want to replace Coroutines.

1 Like

Here’s what I think I’ve figured out but I could be wrong:

  1. If you just call an async method it runs normally until you hit an await that actually needs to await*, at which point it will yield back to the caller like await does, which will finish the current Update frame or Start method or wherever you called the async method from. When the await is done awaiting, it automatically uses Unity SynchronizationContext and continues from where it left off some frames later, at some point around the same time as coroutines get updated, and continues running on the main thread.

  2. Await will actually just return immediately if the task you give it is already complete, which means if you want to actually wait a frame you have to await on something guaranteed to cause a wait. I’ve been using Task.Delay(1), which seems to be roughly similar to “yield return null” from a coroutine: it will yield back so that the Update finishes and come back during the next Update.

  3. Task.Yield() and Task.Delay(0) both seem to return immediately and don’t yield, which seems different than what I’ve read on the internets, but maybe I’m dumb and not understanding. So don’t use those.

So if you just want to replace a coroutine with an async method:

void Start() {
    StartCoroutine(DoThingsCoroutine());
}

IEnumerator DoThingsCoroutine() {
    while (something) {
        Debug.Log("Doing stuff " + Time.deltaTime);
        yield return null;
   }
}

becomes

void Start() {
    DoThingsAsync();
}

async Task DoThingsAsync() {
    while (something) {
        Debug.Log("Doing stuff " + Time.deltaTime);
        await Task.Delay(1);
    }
}

Am I doing it right or is this stupid? It seems to work but I feel like maybe I’m going in the wrong direction.

1 Like

Docs:

Simple sample:

    class TestAsync : Monobehaviour
    {
        void Start()
        {
            DoTaskAsync();
        }
       
        public async void DoTaskAsync()
        {
            await Task.Run(()=>
            {
                //a long-running operation...
            });
        }
    }

I already know that doesn’t work; anything you put inside of Task.Run gets moved to a new thread on the ThreadPool, where it will throw an “EnsureMainThread” exception as soon as you try to access anything in the scene. Plus, it’s doubly bad because the exception itself will just get swallowed and the whole thing will just silently fail, because you’re not returning a Task from the async method, so there is no way to wrap it in a try/catch or check the state of the task.

6 Likes

I’ve tried things with async and await without success so i ended up with something like this

public void myVoid()
{
        Task task = new Task(() => mylongTask());
        task.Start();
        task.Wait();
}
private task mylongTask()
{
}

on windows i can see all of my cpu cores being used sometimes at 80-90% with this method

I made simple (and silly) example how to use tasks. It doesn’t replace coroutines since you can’t access many unity features inside task, because they need to be run in main thread. However, it might still be useful though, if you need to do some heavy calculations, I/O operations and stuff like that.

using UnityEngine;
using System.Threading.Tasks;

public class TaskExample : MonoBehaviour {
    float _elapsed = 0;
    void Start()
    {
        Debug.Log("start begin");

        var tcs = new TaskCompletionSource<Vector3>();
        Task.Run(async () =>
        {
            Debug.Log("Task started");
            tcs.SetResult(await LongRunningTask());
            Debug.Log("Task stopped");
        });
      
        // ConfigureAwait must be true to get unity main thread context
        tcs.Task.ConfigureAwait(true).GetAwaiter().OnCompleted(() =>
        {
            Debug.Log("task completed");
            transform.Rotate(tcs.Task.Result);
        });

        Debug.Log("start end");
    }

    void Update()
    {
        if (_elapsed > 1)
        {
            Debug.Log($"update loop running " + Time.realtimeSinceStartup);
            _elapsed = 0;
        }
        _elapsed += Time.deltaTime;
    }

    async Task<Vector3> LongRunningTask()
    {
        var rand = new System.Random();
        var v = Vector3.zero;
        // do something that takes long time
        for (var i = 0; i < 30000000; i++)
        {
            v = new Vector3(rand.Next(0, 360), rand.Next(0, 360), rand.Next(0, 360));
        }
        return v;
    }
}
1 Like

All of these examples are how to create a background thread where you can’t access Unity objects, so they’re not particularly useful in my case. Here’s an example coroutine that fades in the alpha on an image:

public IEnumerator FadeIn(Image image) {
  while(image.color.a < 1f) {
    image.color.a += speed * Time.deltaTime;  //needs to be on the main thread
    yield return null;  //needs to regularly unblock so that the game keeps rendering
   }
}

What’s the best way to convert that into an async method? Like I said in the third post, I got it working by converting “yield return null” into “await Task.Delay(1)”, and by calling the async method directly (without Task.New or task.Start()) but I’m not sure if this is dumb. I feel like “await Task.Delay(1)” is arbitrary since I don’t actually care about waiting 1 millisecond, I’m just trying to force the await to yield control. And calling an async method directly gives me Resharper warnings about how I’m being stupid and shouldn’t do that, and I feel sad when Resharper yells at me, even if the code technically works. :frowning:

1 Like

@makeshiftwings No, you’re doing it right in your example. I don’t know the reason why Task.Yield() is not working, it seems like it should, if I understand it correctly.

I also think it’s wrong to think about tasks as if they’re always executed on background threads, because by default they’re not. It’s specifically Task.Run(…) that does this.
Compare these:

    Task<string> LoadAsync(string path)
    {
        return Task.Run(() => File.ReadAllText(path));
    }

    async Task LoadThroughTask(string path)
    {
        // here our method LoadAsync spawns a new thread through Task.Run to load the file in background
        var loadingTask = LoadAsync(path);

        while (!loadingTask.IsCompleted)
            await Task.Delay(1);

        var text = loadingTask.Result;
        Debug.Log(text);
    }



    IEnumerator LoadThroughCoroutine(string path)
    {
        // here Resources.LoadAsync spawns a new thread to load the resource in background
        var loadingOperation = Resources.LoadAsync<TextAsset>(path);

        while (!loadingOperation.isDone)
            yield return null;

        var text = ((TextAsset)loadingOperation.asset).text;
        Debug.Log(text);
    }

As you can see, tasks are actually pretty similar to coroutines. You can rewrite both of these methods like this:

    async Task LoadThroughTask(string path)
    {
        var loadingTask = LoadAsync(path);
        await loadingTask;

        var text = loadingTask.Result;
        Debug.Log(text);
    }

    IEnumerator LoadThroughCoroutine(string path)
    {
        // here Resources.LoadAsync spawns a new thread to load the resource in background
        var loadingOperation = Resources.LoadAsync<TextAsset>(path);
        yield return loadingOperation;

        var text = ((TextAsset)loadingOperation.asset).text;
        Debug.Log(text);
    }

But the main advantage of tasks (awaiters) is that you can use return values directly (which you cannot do with coroutines):

    async Task LoadThroughTask(string path)
    {
        var text = await LoadAsync(path);
        Debug.Log(text);
    }

You can also create tasks by other means, for example TaskCompletionSource. Here everything happens on the main thread:

    TaskCompletionSource<string> _taskCompletionSource;
    Button _button;
    InputField _input;

    Task<string> GetUserName()
    {
        _taskCompletionSource = new TaskCompletionSource<string>();
        _button.gameObject.SetActive(true);
        _input.gameObject.SetActive(true);

        _button.onClick.AddListener(() => _taskCompletionSource.SetResult(_input.text));

        return _taskCompletionSource.Task;
    }

    async Task Test()
    {
        Debug.Log("this happens before showing the button and the input field");
        var userName = await GetUserName();
        Debug.Log("user has pressed the button: " + userName);
    }

You can create tasks from other tasks:

    async Task Test()
    {
        var userNameTask = GetUserName();
        var loadTask = LoadAsync("text.txt");
        var combinedTask = Task.WhenAll(userNameTask, loadTask);
        await combinedTask;
        Debug.Log("user has pressed the button: " + userNameTask.Result + " and the file has loaded: " + loadTask.Result);
    }
4 Likes

I think that we need Unity official examples of this approach in order to substitute Coroutines with this so we can be for sure what is the correct replacement.

2 Likes

I mean I am all for learning new things but besides the lack of return values from Coroutines is there a reason you’re adamant that they’re bad and need to be replaced with await/async?

I don’t understand one thing: why do you want to replace that with an async method? Async is most useful when you do the heavy processing on another thread (but yes, you can only do that with objects that don’t derive from UnityEngine.Object). But you don’t want to do that, and coroutine works just fine in your case.

1 Like

Because I originally thought async would result in cleaner and faster code, since we’d be avoiding the overhead of allocating Coroutine enumerators and objects. I have to disagree with the idea that async is only useful for doing heavy processing on another thread. THREADS are useful for doing heavy processing on another thread, but we could already use threads just fine with ThreadPool.QueueUserWorkItem. Microsoft came up with the whole async/await and Task framework to allow lots of different ways of doing things asynchronously, and I’d even go so far as to say that Task.Run(() => SomeStuff()) is not even the main point of async, it’s just an easy example for people who are used to typing ThreadPool.QueueUserWorkItem. But I refuse to believe that MS spent years working on this framework if the only point of it is that it’s faster to type Task.Run than ThreadPool.QueueUserWorkItem. :wink:

Things that need to take place over several frames seem ideal for async, but due to some of Unity’s weirdness it seems hard to get it to work as I’d expect. If we have async methods that were similar to the existing built-in coroutines I think it would make more sense, like:

await UnityAsync.NextUpdate;
await UnityAsync.NextFixedUpdate;
await UnityAsync.NextLateUpdate;

Those make more sense than Task.Delay(1), which still seems arbitrary to me. I just sort of picked it after figuring out that Task.Yield() and Task.Delay(0) didn’t work.

1 Like

I completely agree.

But also with:

await UnityAsync.WaitForSeconds(1);
await UnityAsync.WaitForSecondsRealtime(1);
await UnityAsync.WaitForFrames(1); // -> yield return null;
1 Like

Async/await generally allocates way more than coroutines… Did you actually measure that async/await is faster than coroutines or you’re just guessing?

Well, I would think that actually was the main idea of it. Compare these:

ThreadPool.QueueUserWorkItem(() =>
{
    DoSomething();
    InvokeOnMainThread(() =>
    {
         Debug.Log("done something");
         ThreadPool.QueueUserWorkItem(() =>
         {
             DoAnotherThing();
             InvokeOnMainThread(() =>
             {
                   Debug.Log("Done another thing");
             });
         });
    });
});

and

await DoSomethingAsync();
Debug.Log("done something");
await DoAnotherThingAsync();
Debug.Log("Done another thing");
2 Likes

it does make for cleaner coding :slight_smile:

I don’t want to be in charge of arguing that async would be useful in Unity since like I said, I’m still trying to figure out how it works. For example, this:

If you put that into an async method and then call it with Task.Run, it does not actually return to the main thread in between await calls like implied; it throws an exception if you try to access anything Unity-related. From what I’ve read, there is no guarantee that await will return on any particular thread unless you set up a synchronization context, and calling Task.Run forces it to spin a new thread and switch to that.

Anyway, it sounds like Unity’s official stance for how to use async is “Don’t”. That we should only use it for doing non-Unity background processes on separate threads? And IEnumerator coroutines are here to stay as the main way to do asynchronous code in Unity?

1 Like

I think that Unity should offer an alternative to Coroutines… And having .NET 4.6 is an opportunity to do that.

Coroutines are something that it’s really tied to Unity (specifically to MonoBehaviours and it’s state) and really “weird”.

I wouldn’t put it as far as “don’t”. But async/await does not directly replace coroutines.

1 Like

But are you talking internally about all this? We need to take advantage of having .net 4.6 and c#6.