Python3.10正常运行的asyncio代码在3.8报错,如何修复?
问题:Python 3.8中asyncio Semaphore引发跨事件循环错误
以下代码在Python 3.10.6(Fedora 35)可正常运行:
import asyncio sem: asyncio.Semaphore = asyncio.Semaphore(2) async def async_run() -> None: async def async_task() -> None: async with sem: await asyncio.sleep(1) print('spam') await asyncio.gather(*[async_task() for _ in range(3)]) asyncio.run(async_run())
但在Python 3.8.10(Ubuntu 20.04)运行时抛出错误:
Traceback (most recent call last): File "main.py", line 21, in <module> asyncio.run(async_run()) File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run return loop.run_until_complete(main) File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete return future.result() File "main.py", line 18, in async_run print(future_entry_index, await future_entry) File "/usr/lib/python3.8/asyncio/tasks.py", line 619, in _wait_for_one return f.result() # May raise f.exception(). File "main.py", line 11, in async_task async with sem: File "/usr/lib/python3.8/asyncio/locks.py", line 97, in __aenter__ await self.acquire() File "/usr/lib/python3.8/asyncio/locks.py", line 496, in acquire await fut RuntimeError: Task <Task pending name='Task-4' coro=<async_run.<locals>.async_task() running at main.py:11> cb=[as_completed.<locals>._on_completion() at /usr/lib/python3.8/asyncio/tasks.py:606]> got Future <Future pending> attached to a different loop
排查后确认是Semaphore对象导致的问题,移除后代码可运行但不符合需求。由于loop参数在Python 3.8已弃用、3.10已移除,无法通过传入loop参数解决。需要修改代码使其在Python 3.8中正常运行。
用户补充:asyncio不同版本差异大,但Semaphore在3.8中应该没问题?
解决方案
问题根源是全局Semaphore在Python 3.8中会绑定到第一个创建的事件循环,但asyncio.run()每次调用都会创建新的事件循环,导致后续任务的循环和Semaphore绑定的循环不一致。
修改方法很简单:把Semaphore的创建放到async_run()内部,确保它和任务在同一个事件循环中初始化:
import asyncio async def async_run() -> None: # 将Semaphore移到内部,和当前任务的事件循环绑定 sem: asyncio.Semaphore = asyncio.Semaphore(2) async def async_task() -> None: async with sem: await asyncio.sleep(1) print('spam') await asyncio.gather(*[async_task() for _ in range(3)]) asyncio.run(async_run())
为什么这样可行?因为asyncio.run()会在进入async_run()协程后才创建事件循环,此时初始化Semaphore会自动绑定到这个新循环,后续的async_task都在同一个循环中执行,就不会出现"不同循环"的错误了。
Python 3.10修复了这个问题,允许Semaphore在不同循环间复用,但3.8及更早版本需要遵循"锁/信号量和任务同循环创建"的规则。
内容的提问来源于stack exchange,提问作者StSav012
相关产品推荐
相关产品推荐

