异步方法在调用者上下文/线程上运行同步,直到其执行路径遇到 I/O 或类似的任务,其中涉及一些等待,然后它不等待,而是返回到原始调用者,稍后恢复其继续。问题是,实现该“等待”方法的首选方式是什么。文件/网络/等异步方法是如何做到的?
让我们假设我有一个方法,它会涉及一些等待,而当前的现成 IO 没有涵盖这些等待。我不想阻塞调用线程,也不想强制我的调用者执行 Task.Run()
来卸载我,我想要一个干净的异步/等待模式,以便我的调用者可以无缝地集成我的库,我可以在它的上下文中运行,直到我需要屈服为止。为了便于讨论,让我们假设我想制作一个未涵盖的新 IO 库,并且我需要一种方法来制作所有保持异步的胶水。
我是否Task.Yield
并继续?我是否必须自己执行 Task.Run
/Task.Wait
等操作?两者看起来更像是相同的抽象(这带来了 Yield
如何产生的问题)。我很好奇,因为有很多关于 async/await continuation 如何为消费者工作以及所有 IO 库如何已经准备好的讨论,但是关于实际的“断点”如何工作以及流程制定者应该如何实现的讨论很少它。同步路径末尾的代码如何实际释放控制以及该方法在该点和之后如何运行。
最佳答案
如果您处于异步堆的底部,没有内置的异步下游调用需要遵守,那么:它落在您身上。 简单的方法是分配一个 TaskCompletionSource<T>
(TCS) 对于一些 T
,连接异步工作(不是基于 Task<T>
的)以您需要的任何方式,将 TCS 粘贴到您稍后可以访问的地方,然后交回 .Task
从 TCS,到调用者。当异步工作完成时——可能通过某种回调,或任何适合该 API 的方式;从你塞满它的地方获取 TCS,并在那里发出完成信号,通过 TrySetResult
等
不过,有很多事情需要考虑:
- 在许多情况下,您可能希望确保通过
TaskCreationOptions.RunContinuationsAsynchronously
对于 TCS 构造函数,如果“线程盗窃”是一个大问题(否则,await
会窃取任何调用.TrySetResult
的线程) - 有多种方法可以创建和管理
Task[<T>]
没有额外分配TaskCompletionSource<T>
的实例 , 但他们更先进 - 或者在极端情况下,如果这是高吞吐量:
ValueTask[<T>]
有一个基于 token 的 API(通过IValueTaskSource[<T>]
),允许多次使用相同的对象模型(作为不同的ValueTask[<T>]
值),以避免任何额外的分配 - 同样,这是一个高级场景
关于c# - 以正确的方式实现异步 "yielding",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69493669/