繁体   English   中英

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

[英]python - how to implement a C-function as awaitable (coroutine)

环境: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脚本。 我想重写它,所以我可以使用它与async def协同程序 ,并让它不阻止uPy脚本。

不确定语法,但这样的事情:

can_q = CANbus.queue()
message = await can_q.get()

如何编写C函数以便我可以await它?

我更喜欢CPython和micropython的答案,但我会接受仅限CPython的答案。

注意:这个答案涵盖了CPython和asyncio框架。 但是,这些概念应该适用于其他Python实现以及其他异步框架。

如何编写C函数以便我可以await它?

编写可以等待结果的C函数的最简单方法是让它返回一个已经等待的对象,例如asyncio.Future 在返回Future之前,代码必须安排将来的结果由一些异步机制设置。 所有这些基于协程的方法都假定您的程序在一些知道如何安排协同程序的事件循环下运行。

但是回归未来并不总是足够 - 也许我们想要定义一个具有任意数量的悬挂点的对象。 返回一个未来只暂停一次(如果返回的未来未完成),一旦未来完成就恢复,就是这样。 await包含多个awaitasync def await无法通过返回未来来实现,它必须实现协同程序通常实现的协议。 这有点像实现自定义__next__的迭代器,而不是使用生成器。

定义一个定制的等待

为了定义我们自己的等待类型,我们可以转向PEP 492,它精确地指定了哪些对象可以传递给await 比定义Python函数其它async def ,用户定义的类型可以使物体awaitable通过定义__await__特殊方法,它的Python / C映射到tp_as_async.am_await所述的一部分PyTypeObject结构。

这意味着在Python / C中,您必须执行以下操作:

  • 为扩展类型的tp_as_async字段指定非NULL值。
  • 让它的am_await成员指向一个接受你的类型实例的C函数,并返回另一个实现迭代器协议的扩展类型的实例,即定义tp_iterPyIter_Self定义为PyIter_Self )和tp_iternext
  • 迭代器的tp_iternext必须提升协程的状态机。 来自tp_iternext每个非异常返回对应于一个暂停,最后的StopIteration异常表示来自协同程序的最终返回。 返回值存储在StopIterationvalue属性中。

为了使协程有用,它还必须能够与驱动它的事件循环通信,以便它可以指定何时在它被挂起后恢复。 asyncio定义的大多数协同程序都希望在asyncio事件循环下运行,并在内部使用asyncio.get_event_loop() (和/或接受显式loop参数)来获取其服务。

例程程序

为了说明Python / C代码需要实现什么,让我们考虑表示为Python async 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,我们必须摆脱神奇的async def函数定义,以及await暂停点。 删除async 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)

__await__的方法_MySleep通过返回的对象my_sleep()将自动被调用await运算符将awaitable对象(东西传递给转换await ),以迭代器。 此迭代器将用于询问等待的对象是选择暂停还是提供值。 这很像是for o in x语句中的for o in x如何调用x.__iter__()iterable x转换为具体的迭代器

当返回的迭代器选择暂停时,它只需要生成一个值。 值的含义(如果有的话)将由协程驱动程序解释,通常是事件循环的一部分。 当迭代器选择停止执行并从await返回时,它需要停止迭代。 使用生成器作为便利迭代器实现, _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 yield from x.__await__() ,我们的生成器必须耗尽future.__await__()返回的迭代器future.__await__() Future.__await__返回的迭代器将在未来不完整的情况下产生,并返回未来的结果(我们在这里忽略,但实际提供的yield from )否则。

__await__()返回自定义迭代器

在C中实现my_sleep的C的最后一个障碍是为_MySleepIter使用生成器。 幸运的是,任何生成器都可以转换为有状态迭代器,其__next__执行代码段直到下一个等待或返回。 __next__实现生成器代码的状态机版本,其中yield通过返回值表示,并通过提高StopIteration return 例如:

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非常简单,但超出了这个答案的范围。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM