Background Information
我正在为Visual Studio构建一个基于Roslyn的CodeFix,它处理类没有实现接口(或缺少该接口的一部分)的情况 .
接口通常是第三方代码,例如微软的IDocumentClient .
然后我创建了一个该接口的实现,其中对方法和属性的调用是通过由3个辅助方法中最相关的候选者处理它们的实际执行来“封装”的,作为装饰实现的一部分 . 这些帮助器方法处理不同返回类型的方案,包括void返回,非Task类型和通用Task类型 .
辅助方法调用 Polly
库;对于返回通用Task类型的helper,特别是 Polly
ExecuteAsync
方法,它执行传递的方法委托,并根据用户指定的行为处理异常(重试,断路器等) .
我的项目代码可以在Github上找到,Polly.Contrib.Decorator .
Problem
我需要能够通过接口声明中包含的信息来识别我创建的方法是否是异步的 .
这将决定两件事:
-
如果我的实现应该用
async
修饰符标记 . -
如果可以异步调用实现,允许我决定我的方法实现 - 包装 - 可以然后,如果它应该由我的包装代码异步处理 .
我不能依赖任何其他外部信息 .
What I have considered
我已经看过使用方法的返回类型来确定它是否是 Task
,但在某些情况下,它的接口中的方法声明可能是'returning void',即使它的实际实现是用async修饰符标记的,或者是以异步方式调用 .
检查Async后缀的名称显然不可靠;不是每个人都遵循这样的惯例 .
Question
是否有一种可靠的方法来识别方法实现是否是异步的,即是否应该使用 async
进行修饰,并且可以使用Roslyn仅根据其接口声明进行异步处理吗?
(请参阅评论讨论,这表明了这个问题的演变)
2 回答
TL;DR
基本问题是如何在接口中包装方法的库应该调用这些方法 - 使用
await
. 不仅是async
是否在不可检测的实现上声明(来自接口,在调用站点的上下文之外),而且它没有为此确定 .(1)
async
关键字是否出现在方法实现中并不足以确定对它的调用是否可以使用await
.(2)如果一个方法的返回类型是
await
能够(这足以确定),则该方法可以await
.(3)
async void
方法不改变上述结论;对于无法使用await
调用它的事实,async void
方法不会异步运行 .(1) Whether the async keyword appears in a method implementation is not sufficiently determining for whether a call to it can or should use await.
async
不是方法签名的正式部分 . 所以在接口中找不到async
. 因此,您可以使用async
关键字编写原始作者的预期方法实现,或使用await
调用 .但是,对于是否可以/应该使用
await
调用方法,是否使用async
关键字编写被调用的方法实际上并不是确定(甚至是充分确定的)因子 . 在没有async
关键字的情况下编写方法的有效情况会返回await
ables:[a] async-await eilision,as used extensively by Polly
[b]一个主要用于I / O实现的接口,因此使用
await
able返回类型声明,但您可能还需要编写内存(如此同步)实现/拦截器 . Discussion of common case: sync in-memory cache around async I/O [c]用于测试目的,用内存(同步)存根删除一些异步依赖(实际上
async
不是方法签名的一部分,因为它允许我们偶尔使用同步实现来实现,如上所述 . )(2) A method can be awaited if-and-only-if its return type is awaitable (this is sufficiently determining); ie the return type is Task, Task<TResult> or has a suitable GetAwaiter() method
唯一确定方法调用是否为
await
-ed的唯一方法是它的返回类型是否为await
能 .(3) async void methods do not change the above conclusions
这个地址
async void
方法的长度,因为问题中的注释可能是方法的返回类型不足,可能是因为void
无法从async void
区分(在界面中) .起点是无法等待
async void
方法,原因很简单,虽然使用async
关键字编写,但它们不会返回任何可以await
-ed的类型 .这是否会使我们的结论无效(2)? (我们可以唯一地使用方法是否返回
await
能够确定如何调用它) . 我们是否正在失去一些异步运行async void
方法的能力,因为我们不能await
吗?具体说来:说接口背后的方法是
async void Foo()
但我们从接口知道的是它是void Foo()
. 如果我们只调用Foo()
,我们是否会失去Foo()
异步运行的能力?答案是否定的,因为
async
方法的运作方式 .async void
方法的行为与使用await
调用的任何async Task/Task<T>
方法一样:它们同步运行,直到它们的第一个内部await
;然后他们返回(void
,或表示他们完成的承诺的Task
),并将被调用方法的剩余部分(第一个await
之后的部分)作为延续来安排 . 该延续是在await
ed事件完成后将异步运行的部分 . (这是一个简洁的描述,但这是广泛的博客; example discussion . )换句话说:
async void
方法的某些部分将异步运行的决定因素不是使用await
调用它,而是在它的正文中,它有一个await
,后面有一些有意义的工作 .(3b) Another angle on async void
由于基本q(为了
Polly.Contrib.Decorator
的目的)是我们应该如何调用包装方法,我们可以围绕async void
方法运行另一种思想实验 . 如果我们能够(以某种方式)确定接口后面的void
方法已被声明async void
怎么办?我们会以不同的方式称呼它吗?回到我们的例子,
async void Foo()
. 我们有什么选择?我们可以直接Foo()
或Task.Run(() => Foo())
.正如Stephen Cleary所述,a library shouldn't use Task.Run() on a caller's behalf . 这样做意味着库正在选择将工作卸载到后台线程,拒绝调用者选择 . (注意:上面讨论了
async void
方法如何操作,这仅适用于被调用方法中第一个await
的工作 . )所以:即使我们知道接口
void Foo()
背后的方法是async void Foo()
,Polly.Contrib.Decorator
仍然只能调用Foo()
. 如果用户想要立即将工作卸载到另一个线程上(例如,他们想要将其从GUI线程卸载),那么他们(在引入Polly.Contrib.Decorator
之前)无论如何都要使用Task.Run(() => ...)
调用该接口方法 . 我们不需要添加额外的一个 .这符合Polly遵循的原则,我建议其他代表性的库应该遵循:它应该对用户代表的运行方式产生最小的效果(除了声明的预期效果) .
以上所有的关键是
async
关键字(本身并不)使方法异步或甚至同时运行,因此不是关键 .async
关键字只允许编译器在await
语句中将方法切换为一系列块;这个方法的块2..n在前面的await
ed调用完成之后作为延续运行(异步;在不同的时间) . 调用者(除了async void
方法)返回Task
,这是一个'promise',它将在await
ed方法完成时完成 .返回
Task
(或实现GetAwaiter()
的其他内容)的事实是决定是否可以使用await
调用它的事实:如果返回的方法类型实现了这个等待模式,则可以等待它 .特别是async / await elision和sync-cache-over-async-io模式的存在证明了被调用方法的返回类型是关键,而不是实现是否使用
async
关键字 .罗斯林有一个内部IsAwaitableNonDynamic extension method,它完全符合您的要求 .
你可以复制它: