Async/Await - When to Return a Task VS Void

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://docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

async Task vs async void

When you call an async void method, or call an async Task method without awaiting it (if the called method contains an await, so it doesn't block), your code will continue right away, without waiting for the method to actually complete. This means that several invocations of the method can be executing in parallel, but you won't know when they actually complete, which you usually need to know.

You can take advantage of executing in parallel like this, while also being able to wait for all the invocations to complete by storing the Tasks in a collection and then using await Task.WhenAll(tasks);.

Also keep in mind that if you want to execute code in parallel, you have to make sure it's safe to do it. This is commonly called "thread-safety".

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.)

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".

_ = Task.Run vs async void | Task.Run vs Async Sub

Firstly: do not use async void. I realize that it expresses the semantics of what you want, but there are some framework internals that actively explode if they encounter it (it is a long, uninteresting story), so: don't get into that practice.

Let's pretend that we have:

private async Task DoSomething() {...}

in both cases, for that reason.


The main difference here is that from the caller's perspective there is no guarantee that DoSomething won't run synchronously. So in the case:

public async task MainThread() {
_ = DoSomething(); // note use of discard here, because we're not awaiting it
}

DoSomething will run on the main thread at least as far as the first await - specifically, the first incomplete await. The good news is: you can just add:

await Task.Yield();

as the first line in DoSomething() and it is guaranteed to return immediately to the caller (because Task.Yield is always incomplete, essentially), avoiding having to go via Task.Run. Internally, Task.Yield() does something very similar to Task.Run(), but it can skip a few unnecessary pieces.

Putting that all together - if it was me, I would have:

public async Task MainThread() {
_ = DoSomething();

// Continue with other stuff and don't care about DoSomething()
}
private async Task DoSomething() {
await Task.Yield();

// Doing long running stuff
}

async Task then await Task vs Task then return task

It is almost the same (in terms of threads etc.). But for the second one (using await) a lot more overhead will be created by the compiler.

Methods declared as async and using await are converted into a state machine by the compiler. So when you hit the await, the control flow is returned to the calling method and execution of your async method is resumed after the await when the awaited Task has finished.

As there is no more code after your await, there is no need to use await anyway. Simply return the Task is enough.



Related Topics



Leave a reply



Submit