将带有回调的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())