12

Hopefully this isn't a repeat, but there are 5000+ questions here with "not all code paths return a value"!

Quite simply, why does this method with a non-generic implementation compile just fine:

    public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
    {
        if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
            await task;
        else
            throw new TimeoutException();
    }

while this attempt to make the method generic generates a Return state missing / ...not all code paths return a value warning / error?:

    public static async Task<T> TimeoutAfter<T>(this Task<T> task, int millisecondsTimeout)
    {
        if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
            await task;
        else
            throw new TimeoutException();
    }
HolySamosa
  • 9,011
  • 14
  • 69
  • 102

2 Answers2

11

The non-generic Task type is somewhat equivalent to an awaitable void method. Just like a void method, you can't return anything from a method that has a return type of Task, which is why the first example compiles. The second example, though, expects a return value of the generic type and you aren't providing one in the path where you await another call.

Quoting from the MSDN reference on the async keyword, specifically about return types.

You use Task if no meaningful value is returned when the method is completed. That is, a call to the method returns a Task, but when the Task is completed, any await expression that's awaiting the Task evaluates to void.

Chris Hannon
  • 4,134
  • 1
  • 21
  • 26
  • 2
    My understanding is that the value of the await expression is *applied* to the return value, so it's not equivalent to returning Void, however await in itself is `System.Void`. – David Anderson Aug 24 '12 at 19:19
  • 1
    That's true that the method does implicitly return a `Task` that you can then await. It's equivalent to void in that you are not allowed to return a value from the method yourself and nothing can be assigned as the result of awaiting that method. In the case of `Task`, the `T` you return will then be assignable from an await (e.g. `var value = await SomeCall();`). – Chris Hannon Aug 24 '12 at 19:32
  • Thanks, great explanation. It makes total sense now, but initially the layer of abstraction of the construct was throwing me off. – HolySamosa Aug 25 '12 at 19:15
8

In the second example you gave you didn't return anything. (See Chris Hannon's answer for why).

public static async Task<T> TimeoutAfter<T>(this Task<T> task, int millisecondsTimeout) {
    if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout)))
        return await task; // return the Task of T
    else
        throw new TimeoutException();
}

In addition to what @ChrisHannon said, from the await documentation.

... if await is applied to the result of a method call that returns a Task<TResult>, then the type of the await expression is TResult. If await is applied to the result of a method call that returns a Task, then the type of the await expression is void. ...

Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
David Anderson
  • 13,558
  • 5
  • 50
  • 76