asyncio.gather出现“Future for request id x is already done”提示的原因、影响及代码优化咨询
先直接回应你的几个问题,再给出具体的优化建议:
1. 关于问题原因的假设
你的初始猜测不对,这个提示不是来自asyncio.gather,而是你使用的OPC UA客户端库内部的futures管理逻辑发出的。asyncio.gather本身不会输出这种带“request id”的提示,而且你后来也通过排查确认了来源是OPC UA库——这类提示通常是库内部处理请求时,重复操作了已经完成的future对象导致的。
2. 提示的影响
从你描述的“没有堆栈跟踪”来看,这应该只是库内部的日志提示,不是未捕获的异常,你的程序大概率不会因为这个提示而停止运行。不过保险起见,你可以留意每次出现这个提示后,对应任务的返回结果是否正常、有没有数据丢失的情况,确保业务逻辑不受影响。
3. 代码优化建议
针对这个问题,结合你的代码,建议从以下几个方向调整:
(1)确保客户端连接正确关闭
你的代码里await client.disconnect()只放在了try块的成功路径里,一旦发生异常(比如TimeoutError),连接可能没有被正确关闭,残留的连接或future可能会导致库内部出现异常提示。把disconnect放到finally块里,确保无论成功还是失败都能清理连接:
async def get_daten(url, uri, maschine): client = Client(url=url) client.session_timeout = 30000 stueckzahl = None zeitpunkt = datetime.now() try: await client.connect() idx = await client.register_namespace(uri) stueckzahl_node = client.get_node(f"ns={idx};i=7") stueckzahl = await client.get_values([stueckzahl_node]) stueckzahl = stueckzahl[0] except OSError as e: # 可添加日志记录异常详情 pass except asyncio.exceptions.TimeoutError as e: print(f"TimeoutError: {maschine}, {url}") except asyncio.exceptions.CancelledError as e: print(f"CancelledError: {maschine}, {url}") except BadNotWritable as e: print(f"BadNotWritable: {maschine}, {url}, {e}") print(await client.get_namespace_array()) finally: # 确保连接被关闭,即使断开时出错也记录日志 try: await client.disconnect() except Exception as disconnect_err: print(f"Failed to disconnect from {maschine}: {disconnect_err}") return zeitpunkt, stueckzahl
(2)升级OPC UA库版本
这类内部future管理的问题,很可能是库的已知bug,建议检查你使用的OPC UA库的官方issue或更新日志,看看是否有相关问题的修复,升级到最新的稳定版本,大概率能解决这个偶发提示。
(3)替换print为日志模块,方便排查
把代码中的print换成Python标准库的logging模块,这样可以记录更详细的上下文(比如时间、日志级别),方便你追踪这个提示出现时的业务场景:
import logging # 配置日志格式 logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) # 示例:替换原有的print logger.error(f"TimeoutError: {maschine}, {url}")
(4)检查客户端连接复用可能性
你的代码每次调用get_daten都创建一个新的Client实例,如果你的程序是每分钟运行一次,每次创建大量短连接,可能会给服务器和客户端库带来额外压力。可以查看OPC UA库的文档,确认是否支持协程安全的连接复用,或者使用连接池来减少连接创建销毁的开销,这也能降低库内部出现异常提示的概率。
最后再强调下:你的asyncio.gather用法是完全正确的,它不会因为任务提前完成而出现问题,不用调整这部分逻辑。
备注:内容来源于stack exchange,提问作者Jens Neumann

