How is Task.Delay awaitable if it's not marked

2019-06-18 15:04发布

问题:

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)":

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

回答1:

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.