What is the return value of async Task?

Imagine I have this async method:

public async Task Foo(object fooObj)
{
    //do foo's stuff.
    //maybe set a fooable static global variable
    //or write some data to fooObj
}

and call it like:

public async void FooCaller()
{
    await Foo(new dummyObject)
}

Ok, so: this first code block returns a Task, but it isn't actually returned anywhere.

I can imagine that there is some compiler exception for the async keyword case, as otherwise a return value is required.

But if this is the case, how is the return value initialized? (ok, I can check that myself).

More important:

Are there any pitfalls involved when using this pattern? (e.g.: accessing fooObj, defered execution time.. etc.)

Jon Skeet
people
quotationmark

Ok, so: this first code block returns a Task, but it isn't actually returned anywhere.

Indeed. You can think of it as being like Task<void> - if that were valid. It's used to indicate when the asynchronous method has completed.

Basically, any non-void async method wraps whatever you return in a task. So for example:

async Task<string> FooAsync()
{
    // Do some async operations
    return "foo";
}

Note that the type of the expression in the return statement is string, but the declared return type of the method is Task<string>. The compiler does all the work to wrap the asynchronous operation in a suitable Task which completes when either the method finishes or when it throws an exception. (In that case, the returned Task has a "faulted" state.)

The reason this is required is that the call to the async method typically returns before the method completes. Think of the method call as being "Please start doing something" - it will start it and return, but you want to know when the "something" has completed, and what the result was.

An await expression performs the opposite unwrapping - it will only continue through the method when the awaited operation has completed, and unwrap the value (e.g. using Task.Result). But instead of blocking, it will make the method immediately return. This is how composition works well with async methods awaiting the result of other async methods.

Once you've got the hang of the string to Task<string> wrapping and the equivalent unwrapping if you await the task, it should be easy to see the analogy with Task and void. So just as this is valid for a void method:

void Foo()
{
    // Do some actions.
    ...
    // No need for a return statement
}

The equivalent is true for an async Task method:

async Task Foo()
{
    // Do some actions (presumably using await)
    ...
    // No need for a return statement
}

You can have a return statement (without a value) in an async Task method:

async Task Foo()
{
    if (weDontNeedToDoAnything)
    {
        return; // This is fine
    }
    // Do some actions (presumably using await)
    ...
    // No need for a return statement
}

people

See more on this question at Stackoverflow