Async/Await inside a Coroutine

We are using an external library that lets us await a longer task via the async/await pattern. We need to await this job while an animation is running at the same time. If we just await the task naively, the animation gets stuck until the task has been finished.

So we packed everything in a Coroutine like this:

IEnumerator WaitForOurTask() {
  yield return FunctionReturningTheAsyncTask();
  ProcessingTheResultsOfTheTask();
}

We did not get an error for this and expected the Coroutine to await the Task. But this is not the case. Instead, it seems to just wait for the next frame (like with yield return null; ) and then continues with ProcessingTheResultsOfTheTask(), which in our case causes a race condition, because the results of the task are not available yet.

We found this third party library:
https://github.com/zsaladin/Asyncoroutine
which allows to do this:

IEnumerator WaitForOurTask() {
  yield return FunctionReturningTheAsyncTask().AsCoroutine();
  ProcessingTheResultsOfTheTask();
}

This works fine. But the library is from 2017 and does not appear to be actively maintained. The latest issue is over one year old, without any response. And this issue also includes a hint that the library is not really working reliably. Introducing such a library always bears the risk of causing problems with future Unity releases.

So I am wondering if thereā€™s a better (built-in) way to actually await a Task inside a Coroutine as of 2020?

As a side-note: Wrapping the async function in yet another async void function did not work either, because async void functions donā€™t cause the caller to wait, and hence weā€™re running into the race condition again.

This seems to work, too:

IEnumerator WaitForOurTask() {
  Task task = FunctionReturningTheAsyncTask();
  yield return new WaitUntil(() => task.IsCompleted);
  ProcessingTheResultsOfTheTask();
}

Unsure if itā€™s the preferred way, though.

20 Likes

Yes, thatā€™s how you do when waiting for anything that isnā€™t directly compatible with Unityā€™s coroutine system.

Thanks waldgeist! I just had this same problem of taking an existing Coroutine and meshing it with an asynchronous Task and your code worked perfectly!

How did you get this to work? For me doing yield return new WaitUntil(()=> task.isCompleted) simply hangs the entire app and doesnā€™t continue to the next frame until the task is done?

1 Like

Another way would be to override keepWaiting in a custom class deriving from CustomYieldInstruction.

Itā€™s great! thanks alow!
However, it wonā€™t work if the async function is inside a ā€˜tryā€™ clause.

Here is another solution

    IEnumerator RefreshLobbyCoroutine()
    {
        var delay = new WaitForSecondsRealtime(2);
        while (lobby != null)
        {
           // Run task and wait for it to complete
            var t = Task.Run(async () => await Lobbies.Instance.GetLobbyAsync(lobby.Id));
            yield return new WaitUntil(() => t.IsCompleted);

           // Task is complete, get the result
            lobby = t.Result;
            yield return delay;
        }
    }
3 Likes

Hey, itā€™s me, from the future.

I just wanted to say that, after searching for this a while, I found the a 3rd party solution that Unity veterans probably all know about already.

This is probably what most people are looking for when it comes to a general solution for integrating async/await with coroutines in most cases.

I havenā€™t used this in a professional project yet, so take with a grain of salt, but it seems like itā€™s at least somewhat maintained, and claims to work across release platforms (though, if I were you, Iā€™d confirm on all your targets before investing in this library, donā€™t just take this randoā€™s uninformed opinion on it).

3 Likes

Hi, i have the same issue. Have you found the solution?

Thatā€™s because the task is launched on the main thread, use Task.Run() to send the task to a thread pool.

1 Like

Sorry for the necro, but can someone please explain the difference between

Task t = FunctionReturningTheAsyncTask();
and
Task t = Task.Run(async () => await FunctionReturningTheAsyncTask());

both followed by yield return new WaitUntil(() => t.IsCompleted);

Edit; Unity 2021.3.2f1

both of which have been suggested in this thread. Why can I call Unity API functions in the task created in the former fashion, but I get exceptions regarding the main/update thread when using the latter? I donā€™t understand the nuts and bolts of the difference between those invocations, and hoping someone can clarify.

Task.Run creates a new thread (kinda but no need to know all the details) to run that block of code. Unity API can only be called from the main thread to avoid freezing or crashing the app. You can check what thread number you are on with Thread.CurrentThread, main thread should be ā€œ1ā€ if I remember correctly.

Meanwhile, the first method is just wrapping the Task capabilities and running on the main thread.

Both methods check for the task completion on the main thread so thereā€™s no problem there.

1 Like