当前位置: 首页 > 面试题库 >

python-如何将C函数实现为可等待的(协程)

慎志国
2023-03-14
问题内容

环境:C和micropython虚拟机中的协作RTOS是任务之一。

为了使VM不阻止其他RTOS任务,我插入其中RTOS_sleep()vm.c:DISPATCH()以便在执行每个字节码后,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,代码必须安排通过某种异步机制设置将来的结果。所有这些基于协程的方法都假定您的程序在某个事件循环下运行,该事件循环知道如何调度协程。

但是返回未来并不总是足够的-
也许我们想定义一个具有任意数量的悬挂点的对象。返回未来仅暂停一次(如果返回的未来未完成),一旦完成未来就恢复,仅此而已。等效于一个async def包含多个对象的可等待对象不能通过返回Futureawait来实现,它必须实现协程通常实现的协议。这有点像实现自定义项的迭代器__next__,而不是生成器。

定义等待的自定义

要定义我们自己的等待类型,我们可以转到PEP
492,它确切指定了可以传递给的对象await。除了使用定义的Python函数之外async def,用户定义类型可以通过定义__await__特殊方法使对象等待,该方法将Python /
C映射到结构的tp_as_async.am_await一部分PyTypeObject

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

  • tp_as_async扩展类型的字段指定一个非NULL值。
  • 使它的am_await成员指向C函数,该函数接受您的类型的实例并返回实现迭代器协议的另一个扩展类型的实例,即,定义tp_iter(通常定义为PyIter_Self)和tp_iternext
  • 迭代器tp_iternext必须推进协程的状态机。每个非例外的回报都tp_iternext对应一个暂停,最终StopIteration例外表示协程的最终回报。返回值存储在的value属性中StopIteration

为了使协程有用,它还必须能够与驱动它的事件循环进行通信,以便它可以指定挂起后何时恢复。由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决定我们现在将暂停还是继续执行”。不完整的将来总是决定暂停,而异步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操作员自动调用,以将可 等待的
对象(传递给的任何对象await)转换为迭代器。该迭代器将用于询问等待的对象是选择暂停还是提供值。这非常类似于该for o in x语句调用x.__iter__()可迭代对象 转换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映射到yield from x.__await__(),我们的发电机必须用尽返回的迭代器future.__await__()Future.__await__如果将来未完成,则返回的迭代器将产生收益,否则返回将来的结果(在此我们忽略,但yield from实际上提供了结果)。

__await__() 返回自定义迭代器

用C语言实现C语言的最终障碍my_sleep是使用生成器_MySleepIter。幸运的是,任何生成器都可以转换为有状态迭代器,__next__该迭代器将执行这段代码直到下一次等待或返回。__next__实现生成器代码的状态机版本,在该状态机中yield通过返回值并return通过raise来表示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非常简单,但超出了此答案的范围。



 类似资料:
  • 任务或任务 我们也可以定义自己的可实现对象。对象应具有以下资格。 < li >它有一个GetAwaiter()方法(实例方法或扩展方法); < li >其GetAwaiter()方法返回一个Awaiter。在下列情况下,对象是一个标识符: < ul > < li >它实现INotifyCompletion或ICriticalNotifyCompletion接口; < li >它有一个IsCompl

  • 代码: 文件:script.py

  • 本文向大家介绍Python实现元素等待代码实例,包括了Python实现元素等待代码实例的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了python实现元素等待代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一、为什么要元素等待? 在UI自动化过程中,元素的出现受网络环境、设备性能等多种元素影响。因此,元素加载和脚本运行到该元素

  • 问题内容: 我的代码在javascript中看起来像这样: 在完成所有这些异步调用之后,我想计算所有数组的最小值。 我要如何等待所有人? 我现在唯一的想法是拥有一个名为done的布尔数组,并在第i个回调函数中将done [i]设置为true,然后说while(不是全部都完成了){} 编辑:我想一个可能但很丑陋的解决方案是在每个回调中编辑完了的数组,然后如果每个回调中都设置了所有其他完成,则调用一个

  • 我有以下异步代码示例: 和两个测试: 下面是运行separatley和后chrome控制台中的结果: 问题:为什么我们在(test2)中使用函数,得到的结果与我们直接复制粘贴到(test1)中的结果不同? (上面的例子很抽象,但是我发现这种行为调用ajax请求(而不是和)在我的应用程序中非常重要(请求必须在请求之前...))

  • 本文向大家介绍C#实现程序等待延迟执行的方法,包括了C#实现程序等待延迟执行的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C#实现程序等待延迟执行的方法。分享给大家供大家参考。具体如下: 希望本文所述对大家的C#程序设计有所帮助。

  • 问题内容: 我目前正在学习React,对于新手来说有些东西不那么容易… 我有一个简单的组件(请注意,由于 function,它呈现了一个数组): 功能是: 关键是需要获取数据才能正常工作。实际上,这时不起作用(崩溃),因为它在获取数据之前运行(运行时为“空”)。 如何在运行之前等待数据被提取?感谢您的提示。 问题答案: 您将需要有条件地渲染。提供要在异步所需数据之前加载的加载状态。您将需要以下内容

  • 问题内容: 我对后端非常熟悉。但是我遇到了一种情况,我必须在前端使用它。 我正在获取对象的数组,并且在那些对象中我获取了位置。现在,我可以获取单个地名,但是我想在map函数中使用该名称来获取地名。因此,正如我们所知道的那样,我必须在那边使用。 这是代码 但是,当我在此处将map函数与async一起使用时,它不会返回任何内容。谁能帮我解决我的问题? 谢谢!!! 问题答案: 您应该始终将获取数据等关注