将带有回调的Python函数转换为asyncio等待函数
我想在异步上下文中使用将带有回调的Python函数转换为asyncio等待函数,python,python-3.x,asynchronous,python-asyncio,pyaudio,Python,Python 3.x,Asynchronous,Python Asyncio,Pyaudio,我想在异步上下文中使用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
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中我会使用,但Python似乎没有类似的功能。您可能需要使用
类asyncio.Future(*,loop=None)“”
未来代表异步操作的最终结果。不是线程安全的
未来是一个等待的目标。协同路由可以等待将来的对象,直到它们有结果或异常集,或者直到它们被取消
通常,未来用于启用基于低级回调的代码(例如,在使用异步IO传输实现的协议中)与高级异步/等待代码进行互操作
经验法则是永远不要在面向用户的API中公开未来对象,创建未来对象的推荐方法是调用loop.create_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
在线程或子进程中运行。如果没有,您可能还需要使用包装对open
的调用,因为两个原因,promisify
的等价物不适用于此用例:
- PyAudio的异步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
返回一对的,回调还必须返回一个有意义的值、一个帧元组和一个布尔标志。通过更改fill
功能,也可以从异步IO端接收数据,从而将其纳入设计中。没有包括实现,因为如果不了解域,它可能没有多大意义。谢谢,这非常有帮助!虽然可以更清楚地说明这一点的是,让您的示例make_iter()
改用类,因为我很难理解它是一个最初返回函数元组的函数。@Miguel,因为回调将在PyAudio管理的后台线程中调用,而不是在事件循环线程中调用call\u soon\u threadsafe
正是为这种用途而设计的。它将函数调度到事件循环而不中断它(例如,通过在不持有适当锁的情况下损坏其数据结构),并在事件循环当时处于休眠状态时将其唤醒。事件循环线程也在操纵队列,因为事件循环从队列中移除内容(并使用call_soon
本身满足自身需要)。但即使没有损坏风险,如果不使用threadsafe变体,事件循环也不会唤醒,因为它不知道需要唤醒。典型的症状是存在不相关的心跳协程“修复”这个问题,如.Ohh,它唤醒了事件循环!这就解释了为什么当我删除call_soon\u threadsafe
时,我的测试将永远挂起。谢谢!基于这个答案,我为sounddevice
模块创建了一个示例:。这似乎工作得很好!
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())