python – 如何实现一个C函数作为等待(协同程序)

weixin_38049686 2019-09-12 12:02:24
环境:C和micropython虚拟机中的协作RTOS是其中的任务之一. 为了使VM不阻止其他RTOS任务,我在vm.c:DISPATCH()中插入RTOS_sleep(),以便在执行每个字节码后,VM放弃对下一个RTOS任务的控制. 我创建了一个uPy接口,使用生产者 – 消费者设计模式从物理数据总线异步获取数据 – 可以是CAN,SPI,以太网. 在uPy中的用法: can_q = CANbus.queue() message = can_q.get() C中的实现是这样的,can_q.get()不阻止RTOS:它轮询C队列,如果没有收到消息,它调用RTOS_sleep()给另一个任务机会填充队列.事情是同步的,因为C队列仅由另一个RTOS任务更新,而RTOS任务仅在调用RTOS_sleep()时切换,即协作 C实现基本上是: // gives chance for c-queue to be filled by other RTOS task while(c_queue_empty() == true) RTOS_sleep(); return c_queue_get_message(); 虽然Python语句can_q.get()不会阻止RTOS,但它会阻止uPy脚本.我想重写它,所以我可以使用它与异步def,即协同程序,并让它不阻止uPy脚本. 不确定the syntax但是这样的事情: can_q = CANbus.queue() message = await can_q.get() 题 如何编写C函数以便我可以等待它? 我更喜欢CPython和micropython的答案,但我会接受仅限CPython的答案.
...全文
90 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
weixin_38063224 2019-09-12
  • 打赏
  • 举报
回复
注意:这个答案涵盖了CPython和asyncio框架.但是,这些概念应该适用于其他Python实现以及其他异步框架. How do I write a C-function so I can await on it? 编写可以等待结果的C函数的最简单方法是让它返回一个已经等待的对象,例如asyncio.Future.在返回Future之前,代码必须安排将来的结果由一些异步机制设置.所有这些基于协程的方法都假定您的程序在一些知道如何安排协同程序的事件循环下运行. 但是回归未来并不总是足够 – 也许我们想要定义一个具有任意数量的悬挂点的对象.返回一个未来只暂停一次(如果返回的未来未完成),一旦未来完成就恢复,就是这样.等待包含多个等待的异步def的等待对象无法通过返回未来来实现,它必须实现协同程序通常实现的协议.这有点像实现自定义__next__的迭代器,而不是使用生成器. 定义一个定制的等待 为了定义我们自己的等待类型,我们可以转向PEP 492,其中specifies确切地可以传递哪些对象等待.除了使用async def定义的Python函数之外,用户定义的类型可以通过定义__await__特殊方法使对象等待,Python / C将这些方法映射到PyTypeObject结构的tp_as_async.am_await部分. 这意味着在Python / C中,您必须执行以下操作: >为扩展类型的tp_as_async字段指定非NULL值.>让它的am_await成员指向一个接受你的类型实例的C函数,并返回另一个实现iterator protocol的扩展类型的实例,即定义tp_iter(通常定义为PyIter_Self)和tp_iternext.>迭代器的tp_iternext必须提升协程的状态机.来自tp_iternext的每个非异常返回对应于一个暂停,最后的StopIteration异常表示来自协同程序的最终返回.返回值存储在StopIteration的value属性中. 为了使协程有用,它还必须能够与驱动它的事件循环通信,以便它可以指定何时在它被挂起后恢复. asyncio定义的大多数协同程序都希望在asyncio事件循环下运行,并在内部使用asyncio.get_event_loop()(和/或接受显式循环参数)来获取其服务. 例程程序 为了说明Python / C代码需要实现什么,让我们考虑表示为Python异步def的简单协程,例如asyncio.sleep()等效: async def my_sleep(n): loop = asyncio.get_event_loop() future = loop.create_future() loop.call_later(n, future.set_result, None) await future # we get back here after the timeout has elapsed, and # immediately return my_sleep创建一个Future,安排它在n秒内完成(其结果设置),并暂停直到将来完成.最后一部分使用await,其中await x表示“允许x决定我们现在是暂停还是继续执行”.一个不完整的未来总是决定暂停,并且asyncio Task协程驱动程序特殊情况产生的期货无限期地暂停它们并连接它们的完成以恢复任务.其他事件循环(curio等)的暂停机制可能在细节上有所不同,但基本思想是相同的:await是可选的执行暂停. __await __()返回一个生成器 要将其转换为C,我们必须摆脱神奇的异步def函数定义,以及等待暂停点.删除异步def非常简单:等效的普通函数只需要返回一个实现__await__的对象: def my_sleep(n): return _MySleep(n) class _MySleep: def __init__(self, n): self.n = n def __await__(self): return _MySleepIter(self.n) my_sleep()返回的_MySleep对象的__await__方法将由await运算符自动调用,以将等待对象(传递给await的任何对象)转换为迭代器.此迭代器将用于询问等待的对象是选择暂停还是提供值.这很像是x语句中的for o如何调用x .__ iter __()将iterable x转换为具体的迭代器. 当返回的迭代器选择暂停时,它只需要生成一个值.值的含义(如果有的话)将由协程驱动程序解释,通常是事件循环的一部分.当迭代器选择停止执行并从等待返回时,它需要停止迭代.使用生成器作为便利迭代器实现,_MySleepIter将如下所示: def _MySleepIter(n): loop = asyncio.get_event_loop() future = loop.create_future() loop.call_later(n, future.set_result, None) # yield from future.__await__() for x in future.__await__(): yield x 当await x maps从x .__等待__()时,我们的生成器必须耗尽未来返回的迭代器.__ await __(). Future .__ await__返回的迭代器将在未来不完整的情况下产生,并返回未来的结果(我们在这里忽略,但实际提供的产量)否则. __await __()返回自定义迭代器 在C中实现my_sleep的C的最后一个障碍是为_MySleepIter使用生成器.幸运的是,任何生成器都可以转换为有状态迭代器,其__next__执行代码段直到下一个等待或返回. __next__实现生成器代码的状态机版本,其中yield通过返回值表示,并通过提高StopIteration返回.例如: class _MySleepIter: def __init__(self, n): self.n = n self.state = 0 def __iter__(self): # an iterator has to define __iter__ return self def __next__(self): if self.state == 0: loop = asyncio.get_event_loop() self.future = loop.create_future() loop.call_later(self.n, self.future.set_result, None) self.state = 1 if self.state == 1: if not self.future.done(): return next(iter(self.future)) self.state = 2 if self.state == 2: raise StopIteration raise AssertionError("invalid state") 翻译成C语言 以上是一些打字,但它可以工作,并且只使用可以使用本机Python / C函数定义的构造. 实际上将这两个类翻译成C非常简单,但超出了这个答案的范围.

433

社区成员

发帖
与我相关
我的任务
社区描述
其他技术讨论专区
其他 技术论坛(原bbs)
社区管理员
  • 其他技术讨论专区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧