I am trying get my hands dirty with async CTP and I noticed that the compiler complains about the async return type. What is the problem with other types?
A simple demo
static void Main(string[] args)
{
DoWork();
Console.WriteLine("Returned to main");
Console.Read();
}
// why do I need to return void, Task or Task<T> here?
// I know I can use something like Task<IEnumerable<string>>
private static async string[] DoWork()
{
Console.WriteLine("DoWork started");
return await Task.Factory.StartNew(
delegate
{
Thread.Sleep(2000);
Console.WriteLine("DoWork done");
return new List<string>();
});
}
For methods other than event handlers that don't return a value, you should return a Task instead, because an async method that returns void can't be awaited. Any caller of such a method must continue to completion without waiting for the called async method to finish.
The behavior of async / await is similar to combining generators and promises. Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.
Async void methods can wreak havoc if the caller isn't expecting them to be async. When the return type is Task, the caller knows it's dealing with a future operation; when the return type is void, the caller might assume the method is complete by the time it returns.
In short, if your async method is an event handler or a callback, it's ok to return void .
On the await
[consumption] side, we are flexible: we can await any type so long as it
has the right methods.
On the async
method [production] side, we are inflexible: we are hard-coded to
return only the Task type (or void).
Why the inconsistency?
Iterators already have this behavior...
An iterator method (one which has a “yield” inside) is hard-coded to return either IEnumerable or IEnumerator. However, you can “foreach” over any type which has GetEnumerator/MoveNext/Current members. So Async is just following suite.
A task is like a future, so it’s good to hard-code it...
A Task is barely more than a future. A future is a basic fundamental part of a language/platform. There’s no reason for a language two have multiple copies of such a fundamental notion. One is enough. It’s so foundational that you might even add keywords to the language to deal with futures. Anyway, if someone has a future-like thing, or a richer notion of task, then they can build it out of Task or Func. (Our Tasks are already running. If you want to build something that’s “cold”, like F# asyncs or like IObservable, one which doesn’t start until you tell it – then you should build it out of a Func rather than out of a Task).
Further subtleties
Define this function:
void f<T>(Func<Task<T>> f)
And invoke it:
f( () => 1 + await t )
We’d like to be able to infer that T=int in this case. Such inference isn’t possible unless
the compiler has hard-coded knowledge that the lambda it passes to “f” has type
Task<int>
.
Source: Technical intro to the Async CTP
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With