Why Exactly Is Void Async Bad

Why should a void method be async to be able to await?

Asynchronous methods must be marked async. The async and await keywords are paired together, primarily for backwards-compatibility reasons; await was a valid identifier (not a keyword), so when it was added to the language, they also added the async keyword.

Alternatively, they could have gone with a multi-word keyword for await, such as await for. However, the language design team decided to go with the pairing, since it clearly and explicitly marks all async methods - it's easier for both compilers and humans to parse.

I have a blog post on the subject, with links to Eric Lippert's definitive blog post as well as discussions in blog comments, Channel9 forums, MSDN forums, and of course right here on Stack Overflow.

Note that the natural return type of an async method without a result value is Task, not void. void is an unnatural return type for async methods. So your default reaction when seeing the "await operator must be within an async method" error should be to mark it async and change the return type from void to Task:

public async Task someOperationAsync()
{
//dostuff
var res = await someOtherOperationAsync();
//do some other stuff that needs res.
}

This follows one of the best practices in my MSDN article on the subject: avoid async void. async void was allowed by the language designers for event handlers (or code that is logically an event handler, like ICommand.Execute); any other usage is going to cause problems.

How(and why) can I avoid returning a void on these async methods?

Constructor are meant to bring an object to it's fully constructed structure once initialized. On the other hand, async methods and constructors don't play well together, as a constructor is inherintly synchronous.

The way to get around this problem is usually to expose an initialization method for the type, which is itself async. Now, you let the caller initialize the object fully. Note this will require you to monitor the actual initialization of the method.

Async shines when you need scale. If you don't expect this to be an IO bottleneck in your application, perhaps consider using synchronous methods instead. This will give you the benefit of actually fully initializing your object once the constructor finishes execution. Although, I don't think I would initiate a call to a database via a constructor anyway:

public async Task InitializeAsync()
{
LoadFromLocal();
await AttemptDatabaseLoadAsync();
}

public async Task AttemptDatabaseLoadAsyncAsync()
{
while(ConnectionAttempts < MAX_CONNECTION_ATTEMPTS)
{
Task<bool> Attempt = TryLoad ();
bool success = await Attempt;
if (success)
{
//call func to load data into program memory proper
}
else
{
ConnectionAttempts++;
}
}
}

And call it:

var dataLoader = new DataLoader();
await dataLoader.InitializeAsync();

One line fire and forget: void vs. async void + await


My understanding is that adding async void and await in this case would just be useless overhead.

No.

If Handler returned a Task, then that would be true and eliding async/await would be fine; the code without async/await would just return the Task directly instead of "unwrapping" it with await and "wrapping" it back into a Task with async.

However, that's not the case here; Handler returns void, so the code without async/await will just ignore the returned Task, which is wrong the vast majority of the time (hence the compiler warning). Specifically, ignoring the Task will ignore any exceptions from that Task. It's also not possible for your code to know when an ignored Task has completed, but presumably that's acceptable since your handler is returning void.

There is a "registration" that async void methods do so that the framework is aware there is a task still in progress, so the framework knows when it's safe to shut down. The only .NET provided framework that actually cares about that is ASP.NET pre-Core; all other .NET-provided frameworks (including all UI frameworks) ignore that "registration".

How to use async/await on void methods in ASP.NET Core?

You can write async void methods but these cannot be awaited:

public static class Program
{
public static async Task Main()
{
const int mainDelayInMs = 500;
AsyncVoidMethod();
await Task.Delay(mainDelayInMs);
Console.WriteLine($"end of {nameof(Main)}");
}

static async void AsyncVoidMethod()
{
await Task.Delay(1000);
Console.WriteLine($"end of {nameof(AsyncVoidMethod)}");
}
}

As you can see AsyncVoidMethod is async but I cannot write await AsyncVoidMethod();.

Async void methods should (most of the time) not be used as you cannot wait for completion of the task and any exception thrown may not be handled(and so it can crash your application): Why exactly is void async bad?

async/await - when to return a Task vs void?


  1. Normally, you would want to return a Task. The main exception should be when you need to have a void return type (for events). If there's no reason to disallow having the caller await your task, why disallow it?

  2. async methods that return void are special in another aspect: they represent top-level async operations, and have additional rules that come into play when your task returns an exception. The easiest way is to show the difference is with an example:

static async void f()
{
await h();
}

static async Task g()
{
await h();
}

static async Task h()
{
throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
f();
}

private void button2_Click(object sender, EventArgs e)
{
g();
}

private void button3_Click(object sender, EventArgs e)
{
GC.Collect();
}

f's exception is always "observed". An exception that leaves a top-level asynchronous method is simply treated like any other unhandled exception. g's exception is never observed. When the garbage collector comes to clean up the task, it sees that the task resulted in an exception, and nobody handled the exception. When that happens, the TaskScheduler.UnobservedTaskException handler runs. You should never let this happen. To use your example,

public static async void AsyncMethod2(int num)
{
await Task.Factory.StartNew(() => Thread.Sleep(num));
}

Yes, use async and await here, they make sure your method still works correctly if an exception is thrown.

For more information see: https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

What's the difference between returning void and returning a Task?

SLaks and Killercam's answers are good; I thought I'd just add a bit more context.

Your first question is essentially about what methods can be marked async.

A method marked as async can return void, Task or Task<T>. What are the differences between them?

A Task<T> returning async method can be awaited, and when the task completes it will proffer up a T.

A Task returning async method can be awaited, and when the task completes, the continuation of the task is scheduled to run.

A void returning async method cannot be awaited; it is a "fire and forget" method. It does work asynchronously, and you have no way of telling when it is done. This is more than a little bit weird; as SLaks says, normally you would only do that when making an asynchronous event handler. The event fires, the handler executes; no one is going to "await" the task returned by the event handler because event handlers do not return tasks, and even if they did, what code would use the Task for something? It's usually not user code that transfers control to the handler in the first place.

Your second question, in a comment, is essentially about what can be awaited:

What kinds of methods can be awaited? Can a void-returning method be awaited?

No, a void-returning method cannot be awaited. The compiler translates await M() into a call to M().GetAwaiter(), where GetAwaiter might be an instance method or an extension method. The value awaited has to be one for which you can get an awaiter; clearly a void-returning method does not produce a value from which you can get an awaiter.

Task-returning methods can produce awaitable values. We anticipate that third parties will want to create their own implementations of Task-like objects that can be awaited, and you will be able to await them. However, you will not be allowed to declare async methods that return anything but void, Task or Task<T>.

(UPDATE: My last sentence there may be falsified by a future version of C#; there is a proposal to allow return types other than task types for async methods.)

(UPDATE: The feature mentioned above made it in to C# 7.)



Related Topics



Leave a reply



Submit