簡體   English   中英

將帶有回調的 Python 函數轉換為可等待的 asyncio

[英]Converting a Python function with a callback to an asyncio awaitable

我想在異步上下文中使用PyAudio庫,但該庫的主要入口點只有一個基於回調的 API:

import pyaudio

def callback(in_data, frame_count, time_info, status):
    # Do something with data

pa = pyaudio.PyAudio()
self.stream = self.pa.open(
    stream_callback=callback
)

我希望如何使用它是這樣的:

pa = SOME_ASYNC_COROUTINE()
async def listen():
    async for block in pa:
        # Do something with block

問題是,我不確定如何將此回調語法轉換為在回調觸發時完成的未來。 在 JavaScript 中,我會使用promise.promisify() ,但 Python 似乎沒有這樣的東西。

由於兩個原因, promisify的等效promisify不適用於此用例:

  • PyAudio 的 async API 不使用 asyncio 事件循環 - 文檔指定回調是從后台線程調用的。 這需要采取預防措施才能與 asyncio 正確通信。
  • 回調不能由單個 Future 建模,因為它被多次調用,而 Future 只能有一個結果。 相反,它必須轉換為異步迭代器,如示例代碼中所示。

這是一種可能的實現:

def make_iter():
    loop = asyncio.get_event_loop()
    queue = asyncio.Queue()
    def put(*args):
        loop.call_soon_threadsafe(queue.put_nowait, args)
    async def get():
        while True:
            yield await queue.get()
    return get(), put

make_iter返回一<async iterator, put-callback>。 返回的對象包含調用回調會導致迭代器生成其下一個值(傳遞給回調的參數)的屬性。 回調可以從任意線程調用,因此可以安全地傳遞給pyaudio.open ,而異步迭代器應該在 asyncio 協程中提供給async for ,它將在等待下一個值時暫停:

async def main():
    stream_get, stream_put = make_iter()
    stream = pa.open(stream_callback=stream_put)
    stream.start_stream()
    async for in_data, frame_count, time_info, status in stream_get:
        # ...

asyncio.get_event_loop().run_until_complete(main())

請注意,根據文檔,回調還必須返回一個有意義的值、一個幀元組和一個布爾標志。 這可以通過更改fill函數來合並到設計中,以便也從 asyncio 端接收數據。 不包括實現,因為如果不了解域,它可能沒有多大意義。

您可能想使用Future

類 asyncio.Future(*, loop=None)¶

Future 表示異步操作的最終結果。 不是線程安全的。

Future 是一個可等待的對象。 協程可以等待 Future 對象,直到它們有結果或異常集,或者直到它們被取消。

通常,Futures 用於啟用基於低級回調的代碼(例如在使用 asyncio 傳輸實現的協議中)以與高級 async/await 代碼互操作。

經驗法則是永遠不要在面向用戶的 API 中公開 Future 對象,推薦的創建 Future 對象的方法是調用 loop.create_future()。 通過這種方式,替代事件循環實現可以注入自己優化的 Future 對象實現。

一個愚蠢的例子:

def my_func(loop):
    fut = loop.create_future()
    pa.open(
        stream_callback=lambda *a, **kw: fut.set_result([a, kw])
    )
    return fut


async def main(loop):
    result = await my_func(loop)  # returns a list with args and kwargs 

我假設pa.open在線程或子進程中運行。 如果沒有,您可能還需要使用asyncio.loop.run_in_executor包裝調用以open

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM