7

I'm looking at Task.Delay(int) decompiled in ILSpy:

// System.Threading.Tasks.Task
[__DynamicallyInvokable]
public static Task Delay(int millisecondsDelay)
{
    return Task.Delay(millisecondsDelay, default(CancellationToken));
}

This method is used like await Task.Delay(5000);, and the intellisense even says "(awaitable)":

enter image description here

So how is it that Task.Delay(int) isn't marked async (public static async Task Delay(int millisecondsDelay))?

i3arnon
  • 113,022
  • 33
  • 324
  • 344
rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • 3
    It's tasks which are awaitable, not the functions that return them. – Lee Jan 30 '15 at 13:19
  • 3
    [Async/Await FAQ](http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx): "An “awaitable” is any type that exposes a GetAwaiter method which returns a valid “awaiter”. This GetAwaiter method may be an instance method (as it is in the case of Task and Task), or it may be an extension method." – Damien_The_Unbeliever Jan 30 '15 at 13:20
  • @I3arnon -- You edited to add c# tag, but this isn't specifically a c# question (that's just the language that ILSpy shows). I could use `Task.Delay` from VB.NET. – rory.ap Jan 30 '15 at 13:39
  • @roryap it isn't, but C# (and .Net) are an umbrella tag regarding the entire MS stack. It also tells the formatter to how to format the code without hints. And to be technical, the question isn't necessarily about the TPL (since everything can be awaited) or .Net (since you can use await outside of it, e.g. WinRT). – i3arnon Jan 30 '15 at 13:44

1 Answers1

19

What's awaitable is the Task Task.Delay returns. Each method returning a Task/Task<TResult> is awaitable. async is just an implementation detail allowing you to use await in that method and the whole state machine it generates.

More generally, every thing that has a GetAwaiter method (extension methods count as well) that return something that has IsCompleted, OnCompleted and GetResult can be awaited.

For example, Task.Yield returns YieldAwaitable which isn't a Task and looks like this:

public struct YieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
{
    public void OnCompleted(Action continuation);
    public void UnsafeOnCompleted(Action continuation);
    public void GetResult();
    public bool IsCompleted { get; }
}

*UnsafeOnCompleted here is just an optimization, await would work without it.

It's important to note that the compiler in this case (same as in other cases like GetEnumerator for foreach) doesn't expect an interface or a base class. It basically uses duck typing (i.e. "if it walks like a duck...") and simply looks for a GetAwaiter method that returns anything (doesn't matter what type or interface or if it's a class or a struct) that has the other 3 members (IsCompleted, OnCompleted and GetResult)

For example, this is how you can make await "bar" compile (it will fail in runtime of course):

public static Awaiter GetAwaiter(this string s)
{
    throw new NotImplementedException();
}
public abstract class Awaiter : INotifyCompletion
{
    public abstract bool IsCompleted { get; }
    public abstract void GetResult();
    public abstract void OnCompleted(Action continuation);
}

In conclusion, you don't need async to return an awaitable and moreover most Task-returning methods in the .Net framework don't use it and explicitly return a Task.

Community
  • 1
  • 1
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Nice. So, without looking in to it yet -- is there an interface that Task or anything else implements that has a `GetAwaiter` method and has `IsCompleted`, `OnCompleted`, and `GetResult`? In other words, how is that achieved? – rory.ap Jan 30 '15 at 13:25
  • Shouldn't the awaitable implement `INotifyCompletion` or `ICriticalNotifyCompletion`? – Yuval Itzchakov Jan 30 '15 at 13:27
  • @roryap - Look into `INotifyCompletion` – Yuval Itzchakov Jan 30 '15 at 13:28
  • @roryap in general, no. `Task` has `GetAwaiter` without an interface. The awaiter itself (e.g. `TaskAwaiter`) implements `INotifyCompletion` (and sometimes `ICriticalNotifyCompletion`) but it doesn't need to. I'll add that to the answer. – i3arnon Jan 30 '15 at 13:30