async/await - when to return a Task vs void?
Normally, you would want to return a
Task
. The main exception should be when you need to have avoid
return type (for events). If there's no reason to disallow having the callerawait
your task, why disallow it?async
methods that returnvoid
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 Task
s 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 returnvoid
,Task
orTask<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 await
ed:
What kinds of methods can be
await
ed? Can a void-returning method beawait
ed?
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
Why Saving Changes to a Database Fails
Return Multiple Values to a Method Caller
Getting Attributes of Enum'S Value
Cannot Convert from List≪Derivedclass≫ to List≪Baseclass≫
Quickest Way to Convert a Base 10 Number to Any Base in .Net
How to Do a Deep Copy of an Object in .Net
C# Difference Between == and Equals()
Send Values from One Form to Another Form
How to Copy the Contents of One Stream to Another
How to Pass Table Value Parameters to Stored Procedure from .Net Code
Virtual Member Call in a Constructor