代码执行后持续触发Asyncio/Aiohttp RuntimeError问题排查
解决「RuntimeError: Event loop is closed」异常
移除代码冗余部分后,Player_Season变量触发管道错误,运行代码时持续抛出如下异常:
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001A221370AF0> Traceback (most recent call last): File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python39_64\lib\asyncio\proactor_events.py", line 116, in __del__ self.close() File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python39_64\lib\asyncio\proactor_events.py", line 108, in close self._loop.call_soon(self._call_connection_lost, None) File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python39_64\lib\asyncio\base_events.py", line 751, in call_soon self._check_closed() File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python39_64\lib\asyncio\base_events.py", line 515, in _check_closed raise RuntimeError('Event loop is closed') RuntimeError: Event loop is closed
可复现的完整代码:
import asyncio import aiohttp class NHLTeam: async def fetch(self, session, url): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Windows; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36" } async with session.get(url, headers=headers) as response: response.raise_for_status() # Catch HTTP errors content = await response.json() return content async def scrape(self, data): print(data) async with aiohttp.ClientSession() as session: tasks = [] for player_name, player_ID, year, position in data: #Iterate over each player Game_Type = 2 # 2 = Regular Season | 3 = Play offs Link = f"https://api-web.nhle.com/v1/player/{player_ID}/game-log/{year}/{Game_Type}" tasks.append(self.fetch_and_parse(session, player_name, Link, year, position)) results = await asyncio.gather(*tasks) return results async def fetch_and_parse(self, session, player_name, player_url, year, position): Player_Season = await self.fetch(session, player_url) #Causes a Pipeline Error team_1_organized_players_data = [['Connor',8483733, 20232024, 'Center'],['Connor',8483733, 20232024, 'Center']] team_1 = NHLTeam() async def run_scraping(): await asyncio.gather( team_1.scrape(team_1_organized_players_data) #team_2.scrape(team_2_organized_players_data) ) asyncio.run(run_scraping())
异常原因分析
这个异常的核心是异步资源在事件循环关闭后才触发清理。当asyncio.run()执行完毕关闭事件循环后,aiohttp底层的_ProactorBasePipeTransport对象在垃圾回收时,尝试调用已关闭的事件循环的call_soon方法,从而抛出错误。
具体到代码中:
- Windows环境下Python 3.9默认使用的Proactor事件循环,与aiohttp的资源清理时序存在兼容性问题;
fetch_and_parse函数仅获取数据但无返回值,导致异步任务的状态不明确,可能引发资源未被及时回收。
解决办法
方法1:修改事件循环策略(Windows专属)
在代码开头设置使用SelectorEventLoop替代默认的ProactorEventLoop,避免资源清理时序冲突:
import asyncio from asyncio import WindowsSelectorEventLoopPolicy asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
方法2:完善异步任务逻辑
给fetch_and_parse函数添加明确的返回值,让异步任务的状态更清晰,确保资源被正确回收:
async def fetch_and_parse(self, session, player_name, player_url, year, position): Player_Season = await self.fetch(session, player_url) # 返回整理后的结果,明确任务完成状态 return { "player_name": player_name, "season": year, "position": position, "game_data": Player_Season }
方法3:显式管理ClientSession生命周期
手动控制ClientSession的关闭,确保所有连接在事件循环关闭前彻底释放:
async def scrape(self, data): print(data) session = aiohttp.ClientSession() try: tasks = [] for player_name, player_ID, year, position in data: Game_Type = 2 Link = f"https://api-web.nhle.com/v1/player/{player_ID}/game-log/{year}/{Game_Type}" tasks.append(self.fetch_and_parse(session, player_name, Link, year, position)) results = await asyncio.gather(*tasks) return results finally: await session.close()
修改后的完整代码
import asyncio from asyncio import WindowsSelectorEventLoopPolicy import aiohttp # 设置事件循环策略,解决Windows下资源清理问题 asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) class NHLTeam: async def fetch(self, session, url): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Windows; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36" } async with session.get(url, headers=headers) as response: response.raise_for_status() content = await response.json() return content async def scrape(self, data): print(data) async with aiohttp.ClientSession() as session: tasks = [] for player_name, player_ID, year, position in data: Game_Type = 2 Link = f"https://api-web.nhle.com/v1/player/{player_ID}/game-log/{year}/{Game_Type}" tasks.append(self.fetch_and_parse(session, player_name, Link, year, position)) results = await asyncio.gather(*tasks) return results async def fetch_and_parse(self, session, player_name, player_url, year, position): Player_Season = await self.fetch(session, player_url) return { "player": player_name, "season": year, "position": position, "game_log": Player_Season } team_1_organized_players_data = [['Connor',8483733, 20232024, 'Center'],['Connor',8483733, 20232024, 'Center']] team_1 = NHLTeam() async def run_scraping(): results = await asyncio.gather( team_1.scrape(team_1_organized_players_data) ) print(results) asyncio.run(run_scraping())
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

