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();

You misunderstood what is meant by the async void that is to be avoided.

It doesn't mean you should never use a task with no result attached. It just says the asynchronous methods that invoke them should return a Task, not void.

Simply modify the signature of your async method:

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

Now calling methods have the option of awaiting the result, when and if they choose to. Either:

await PrintNumberWithAwait();

Or

Task t = PrintNumberWithAwait();
// Do other stuff
// ...
await t;

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 Synchronization context, that uses a 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 framework as UniTask for async await using Unity (The best in my opinion).