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
.
Task.Delay
from VB.NET. – Lansquenet