如果Task.Delay没有标记为async,它如何成为可等待的(awaitable)?

7

我正在查看在 ILSpy 中反编译的 Task.Delay(int) 方法:

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

这种方法的使用方式类似于await Task.Delay(5000);,而智能感知甚至会显示“(awaitable)”:
enter image description here
那么为什么Task.Delay(int)没有标记为asyncpublic static async Task Delay(int millisecondsDelay))呢?

3
可以翻译为:等待的是可等待对象,而不是返回它们的函数。 - Lee
3
“awaitable”指的是任何类型,其暴露了一个GetAwaiter方法,并返回一个有效的“awaiter”。这个GetAwaiter方法可以是一个实例方法(例如Task和Task<TResult>),也可以是一个扩展方法。 - Damien_The_Unbeliever
@I3arnon -- 你添加了C#标签,但这并不是一个特定的C#问题(ILSpy只是显示该语言)。我可以在VB.NET中使用Task.Delay - rory.ap
@roryap 不是这样的,但C#(和.Net)是关于整个MS堆栈的一个总称。它还告诉格式化程序如何在没有提示的情况下格式化代码。而且从技术上讲,问题不一定是关于TPL(因为可以等待所有东西)或.Net(因为可以在其外部使用等待,例如WinRT)。 - i3arnon
1个回答

19
可以等待的是Task.Delay返回的Task。每个返回Task/Task<TResult>的方法都可以等待。 async只是一种实现细节,允许您在该方法中使用await和它生成的整个状态机。
更普遍地说,每个具有GetAwaiter方法(扩展方法也算)的东西,返回一个具有IsCompletedOnCompletedGetResultsomething都可以被等待。
例如,Task.Yield返回YieldAwaitable,它不是一个Task,看起来像这样:
public struct YieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
{
    public void OnCompleted(Action continuation);
    public void UnsafeOnCompleted(Action continuation);
    public void GetResult();
    public bool IsCompleted { get; }
}

* 在这里,UnsafeOnCompleted 只是一种优化,使用 await 也可以不用它。

需要注意的是,在这种情况下编译器(与其他情况如 foreachGetEnumerator 一样)不期望接口或基类。它基本上使用 鸭子类型(即 "如果它走起路来像只鸭子..."),并简单地查找返回任何东西(无论类型、接口还是它是类还是结构)的 GetAwaiter 方法,该方法具有其他 3 个成员(IsCompletedOnCompletedGetResult

例如,这就是你可以使 await "bar" 编译通过的方式(当然它会在运行时失败):

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);
}

总之,你不需要使用async来返回一个可等待的对象,此外,在.Net框架中,大多数返回Task的方法都不使用它,并显式地返回Task

很好。那么,还没有深入研究,是否有一个接口,Task或其他任何东西实现了GetAwaiter方法,并具有IsCompletedOnCompletedGetResult?换句话说,这是如何实现的? - rory.ap
等待的对象是否应该实现 INotifyCompletionICriticalNotifyCompletion 接口? - Yuval Itzchakov
@roryap - 研究一下 INotifyCompletion - Yuval Itzchakov
一般来说,不需要。Task有一个没有接口的GetAwaiter方法。等待器本身(例如TaskAwaiter)实现了INotifyCompletion(有时还包括ICriticalNotifyCompletion),但这并非必须。我会在答案中加上这一点。 - i3arnon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接