如何在Python asyncio中异步等待select.select调用?
解决异步等待非asyncio API socket就绪的问题
你遇到的核心问题是想把同步的select换成asyncio原生的异步等待,避免轮询带来的延迟和效率问题。其实asyncio已经提供了专门的方法来解决这个场景——sock_wait_for_read,它正是用来异步等待socket变为可读状态的,完全符合你的需求。
为什么之前的方案不行?
sock_recv(self._q._connection, 0)会立即返回,因为请求接收0字节不需要等待任何数据;- 带超时的
select加asyncio.sleep本质是轮询,必然会引入额外延迟,而且浪费CPU资源。
最优解决方案:使用sock_wait_for_read
直接用asyncio事件循环的sock_wait_for_read方法,它会把socket注册到事件循环中,真正异步等待数据就绪,没有轮询延迟,效率拉满。修改你的run方法如下:
async def run(self): while True: loop = asyncio.get_event_loop() # 异步等待socket可读,直到有数据到来才会继续执行 await loop.sock_wait_for_read(self._q._connection) # 此时socket已经就绪,调用API的receive方法不会阻塞 msg = self._q.receive() print(msg)
针对Python 3.7+的优化(可选)
如果你之后升级到Python 3.7及以上,可以用asyncio.get_running_loop()代替get_event_loop(),它能更安全地获取当前运行的事件循环:
async def run(self): while True: loop = asyncio.get_running_loop() await loop.sock_wait_for_read(self._q._connection) msg = self._q.receive() print(msg)
补充方案:使用add_reader注册回调
如果你想换一种代码结构,也可以用事件循环的add_reader方法,给socket注册一个可读回调:
def _handle_incoming_data(self): # 当socket可读时触发这个回调 msg = self._q.receive() print(msg) async def run(self): loop = asyncio.get_event_loop() # 注册socket可读回调 loop.add_reader(self._q._connection, self._handle_incoming_data) # 保持协程持续运行(比如用一个永久的Future) await asyncio.Future()
这种方式适合不需要在循环里做其他异步操作的场景,但如果你的逻辑需要在等待数据后继续执行异步任务,第一种sock_wait_for_read的方式更贴合你的原有代码结构。
注意事项
确保API的receive()方法在socket就绪后不会阻塞——一般来说,当select或sock_wait_for_read提示socket可读时,recv类的操作不会阻塞,所以这个方法是安全的。如果receive()内部还有其他阻塞逻辑(比如锁操作),那你可能需要把它放到线程池里执行:
await loop.run_in_executor(None, self._q.receive)
不过根据你的描述,应该不需要这一步。
内容的提问来源于stack exchange,提问作者chasep255
相关产品推荐
相关产品推荐

