簡體   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