Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why return type of async must be void, Task or Task<T>

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>();
        });        
}
like image 453
oleksii Avatar asked Aug 10 '11 12:08

oleksii


People also ask

Why must async methods return Task?

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.

What is the return type of async await?

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.

Why async should not return void?

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.

Can async return void?

In short, if your async method is an event handler or a callback, it's ok to return void .


1 Answers

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?

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

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

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

like image 190
Skomski Avatar answered Oct 15 '22 06:10

Skomski