Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I avoid async void?

Note: Turns out this issue is specific to Unity.

I read that async void was to be avoided. I am trying to do so using Result, but my application keeps locking up. How can I avoid using async void?

public async void PrintNumberWithAwait()
{
    int number = await GetNumber();
    Debug.Log(number); //Successfully prints "5"
}

public void PrintNumberWithResult()
{
    int number = GetNumber().Result;
    Debug.Log(number); //Application Freezes
}

private async Task<int> GetNumber()
{
    await Task.Delay(1000);
    return 5;
}

I thought this was correct, but I must be missing something. How do I use async/await without having async void?

I ran my tests separately with the following code (commented one out at a time):

PrintNumberWithAwait();
PrintNumberWithResult();
like image 729
Evorlor Avatar asked Jul 15 '26 23:07

Evorlor


2 Answers

Short Version

Unity's Synchronization Context is single threaded. So:

  1. Result hangs until the task completes
  2. The task cannot complete because continuation is pushed on Main Thread, that is waiting

Detailed Version

You said you are using Unity. Unity is "99%" a single-threaded framework. So I suppose this code is executed on the Main, UI Thread.

Let's step into what your code do in details, when executing PrintNumberWithResult().

  1. [Main Thread] From PrintNumberWithResult() you call GetNumber()
  2. [Main Thread] You execute await Task.Delay()
  3. [Main Thread] The code under the await line (the return 5) is "pushed" into a "List of code" to execute after that the Task (The Delay) is completed. Small insight: This sort of list of "continuations code" is handled by a class called SynchronizationContext (It is a c# thing, not a Unity thing). You can think this class as the guy that says HOW and WHEN the code between awaits are called. The standard .NET implementation uses a thread pool (so a set of threads) that will execute the code after the task is completed. It's like an "advanced callback". Now in Unity this is different. They implemented a custom Synchronization Context that ensures all the code is executed ALWAYS in the MAIN THREAD. We can continue now
  4. [MAIN THREAD] The Delay Task is not completed yet, so you have an early return in the PrintNumberWithResult method, and you execute the .Result, that makes the Main Thread hangs there until the Task is completed.
  5. [Deadlock]. 2 things are happening in this point. The Main Thread is waiting for the Task to complete. The Custom Unity's synchronization Context pushed the code above the await to be executed in the Main Thread. But The Main Thread is waiting! So it will never be free to execute that code.

Solution Never call .Result.

If you want to fire and forget a task operation use Task.Run(() => ). But Wait! that's not a good idea in Unity! (Maybe it is in other .NET applications).

If you use Task.Run() in Unity you are forcing the async code to be executed using the default .NET TaskScheduler, that uses the thread pool, and that could cause some synchronization issues if you are calling some Unity related API.

What you want to do in that case is to use an async void (not really for reasons related to exception handling), an async Task method that you will never await (better), or maybe use a library as UniTask for async await using Unity (The best in my opinion).

like image 98
Federico Alterio Avatar answered Jul 18 '26 04:07

Federico Alterio


This answer is pretty complete and explains the "issue"/subject very well.

But to extend it a little, your example with the async void "works" for one reason: For Debug.Log the synchronization context doesn't matter. You can safely Debug.Log from different threads and background tasks and Unity handles them in the console. BUT as soon as you would try to use anything from the Unity API that is only allowed on the main thread (basically everything that immediately depends on or changes the scene content) it might break since it is not guaranteed that await ends up on the main thread.

However, what now?

Nothing really speaks against using Thread and Task.Run in Unity!

You only have to make sure to dispatch any results back into the Unity main thread.

So I just wanted to give some actual examples of how this can be done.


Something often used is a so called "Main thread dispatcher" .. basically just a ConcurrentQueue which allows you to Enqueue callback Actions from just any thread and then TryDequeue and invoke these in an Update routine on the Unity main thread.

This looks somewhat like e.g.

/// <summary>
/// A simple dispatcher behaviour for passing any multi-threading action into the next Update call in the Unity main thread
/// </summary>
public class MainThreadDispatcher : MonoBehaviour
{
    /// <summary>
    /// The currently existing instance of this class (singleton)
    /// </summary>
    private static MainThreadDispatcher _instance;

    /// <summary>
    /// A thread-safe queue (first-in, first-out) for dispatching actions from multiple threads into the Unity main thread
    /// </summary>
    private readonly ConcurrentQueue<Action> actions = new ConcurrentQueue<Action>();

    /// <summary>
    /// Public read-only property to access the instance
    /// </summary>
    public static MainThreadDispatcher Instance => _instance;

    private void Awake ()
    {
        // Ensure singleton 
        if(_instance && _instance != this)
        {
            Destroy (gameObject);
            return;
        }

        _instance = this;

        // Keep when the scene changes 
        // sometimes you might not want that though
        DontDestroyOnLoad (gameObject);
    }

    private void Update ()
    {
        // In the main thread work through all dispatched callbacks and invoke them
        while(actions.TryDequeue(out var action))
        {
            action?.Invoke();
        }
    }

    /// <summary>
    /// Dispatch an action into the next <see cref="Update"/> call in the Unity main thread
    /// </summary>
    /// <param name="action">The action to execute in the next <see cref="Update"/> call</param>
    public void DoInNextUpdate(Action action)
    {
        // Simply append the action thread-safe so it is scheduled for the next Update call
        actions.Enqueue(action);
    }
}

Of course you need this attached to an object in your scene, then from anywhere you could use e.g.

public void DoSomethingAsync()
{
    // Run SomethingWrapper async and pass in a callback what to do with the result
    Task.Run(async () => await SomethingWrapper(result => 
    { 
        // since the SomethingWrapper forwards this callback to the MainThreadDispatcher
        // this will be executed on the Unity main thread in the next Update frame
        new GameObject(result.ToString()); 
    }));
}

private async Task<int> Something()
{
    await Task.Delay(3000);

    return 42;
}

private async Task SomethingWrapper (Action<int> handleResult)
{
    // cleanly await the result
    var number = await Something ();

    // Then dispatch given callback into the main thread
    MainThreadDispatcher.Instance.DoInNextUpdate(() =>
    {
        handleResult?.Invoke(number);
    });
}

This makes sense if you have a lot of asynchronous stuff going on and want to dispatch them all at some point back to the main thread.

enter image description here


Another possibility is using Coroutines. A Coroutine is basically a bit like a temporary Update method (the MoveNext of the IEnumerator is called once a frame by default) so you can just repeatedly check if your task is done already on the main thread. This is what Unity uses themselves e.g. for UnityWebRequest and could looked somewhat like

public void DoSomethingAsync()
{
    // For this this needs to be a MonoBehaviour of course
    StartCorouine (SomethingRoutine ());
}

private IEnumerator SomethingRoutine()
{
    // Start the task async but don't wait for the result here
    var task = Task.Run(Something);

    // Rather wait this way
    // Basically each frame check if the task is still runnning
    while(!task.IsCompleted)
    {
        // Use the default yield which basically "pauses" the routine here
        // allows Unity to execute the rest of the frame
        // and continues from here in the next frame
        yield return null;
    }
    // basically same as
    //yield return new WaitUntil(()=>task.IsCompleted);

    // Safety check if the task actually finished or is canceled or faulty
    if(task.IsCompletedSuccessfully)
    {
        // Since we already waited until the task is finished 
        // This time Result will not freeze the app
        var number = task.Result;
        new GameObject (number.ToString());
    }
    else
    {
        Debug.LogWarning("Task failed or canceled");
    }
}

private async Task<int> Something ()
{
    await Task.Delay(3000);

    return 42;
}

enter image description here

like image 20
derHugo Avatar answered Jul 18 '26 02:07

derHugo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!