I recently learned about the possibility to have custom awaitable types and as this question and Stephen Toub states, there are several requirements to be an awaitable type.
So if a type T
wants to be awaitable it must
- expose an parameterless method
GetAwaiter
that returns a valid awaiter
and if a type A
wants to be a valid awaiter it must
- Implement the
INotifyCompletion
interface - Provide a boolean property called
IsCompleted
- Provide a parameterless
GetResult
method that returnsvoid
orTResult
So now I'm asking if all that is required to be an awaitable type, why isn't that part of some interfaces like
public interface INotifyCompletion
{
bool IsCompleted { get; }
void OnCompleted(Action continuation);
}
public interface IAwaiter : INotifyCompletion
{
void GetResult();
}
public interface IAwaitable<TAwaiter> where TAwaiter : IAwaiter
{
TAwaiter GetAwaiter();
}
public interface IAwaiter<TResult> : INotifyCompletion
{
TResult GetResult();
}
// this would probably not necessary but would likely help to identify
// awaitables that return a value
public interface IAwaitable<TAwaiter, TResult> where TAwaiter : IAwaiter<TResult>
{
TAwaiter GetAwaiter();
}
I also do understand that the compiler does not need it, since it can check all that at compile time without any penalty. But since there is the INotifyCompletion
interface for the OnCompleted()
method, why isn't the rest of the interface for awaitables and awaiters packed in some interfaces?
This would most likely help to inform programmers how to implement this.
I also know that one can make an type awaitable by providing an extension method that return a valid awaiter, but again why isn't the whole interface for an awaiter packed in a single interface but has holes (i.e. the IsCompleted
property is not part of any interface but required)?
GetAwaiter()
method, why add an interface? It's not needed. This particular detail won't help most programmers though, as only very specialised code would even need to implement this. Code like the one found in Task and ValueTask. Applications should return one of those types, not try to implement the method – NetsukeGetAwaiter()
? – NetsukeTask
s and was wondering about other types that can be awaited like theValueTask
and how I could minimize code duplication. For examlple I have anWithTimeout()
extension method forTask
(which works perfectly fine) but I was wondering how I could make this method accessible to other awaitables likeConfiguredTaskAwaitable
which would me enable to write code liketask.ConfigureAwait(false).WithTimeout(1000)
without dulicating any code. – Trinaryforeach
statement. TL;DR there are performance penalties associated with interfaces, that are avoided by using patterns. I guess that patterns are used forawait
for similar reasons. – Tritheismstatic IAwaiter<R> Then<T, R>(this IAwaiter<T> src, Func<T, IAwaiter<R>> callback) {
, you'd instead dostatic IR then<T, R, IT, IR>(this IT t, Func<T, IR> callback) where IT: IAwaiter<T> where IR: IAwaiter<R> {
, the JIT will then specialize for every type passed in implementingIAwaiter<?>
. It also opens up more opportunities to inline. – Mithraism