Asyncio RuntimeError:事件循环已关闭
Posted
技术标签:
【中文标题】Asyncio RuntimeError:事件循环已关闭【英文标题】:Asyncio RuntimeError: Event Loop is Closed 【发布时间】:2015-09-16 01:22:12 【问题描述】:我正在尝试使用 Asyncio 和 aiohttp 库发出一堆请求(约 1000 个),但我遇到了一个我找不到太多信息的问题。
当我用 10 个 url 运行这段代码时,它运行得很好。当我使用 100 多个 url 运行它时,它会中断并给我RuntimeError: Event loop is closed
错误。
import asyncio
import aiohttp
@asyncio.coroutine
def get_status(url):
code = '000'
try:
res = yield from asyncio.wait_for(aiohttp.request('GET', url), 4)
code = res.status
res.close()
except Exception as e:
print(e)
print(code)
if __name__ == "__main__":
urls = ['https://google.com/'] * 100
coros = [asyncio.Task(get_status(url)) for url in urls]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))
loop.close()
可以在here找到堆栈跟踪。
任何帮助或见解将不胜感激,因为我已经为此苦苦思索了几个小时。显然,这表明事件循环已关闭,但仍应打开,但我不明白这是怎么可能的。
【问题讨论】:
不是Asyncio
错误。 Python 递归错误,已达到限制。所有非类函数都需要线程...
首先,确保您使用的是最新的 aiohttp 版本。我想你会的。从技术上讲,aiohttp 需要在完成关闭底层套接字的请求后进行一次循环迭代。所以在loop.close()
调用之前插入loop.run_until_complete(asyncio.sleep(0))
。
您的回溯表明,提交到Executor 到run_in_executor 的作业在循环关闭后返回。奇怪的是,aiohttp 和 asyncio 不要使用 run_in_executor
...
@AndrewSvetlov,感谢您的回复 - 我尝试在收盘前睡觉,但仍然没有骰子......还有其他想法吗?
@Vincent 在技术上是这样做的,DNS 解析是由 run_in_executor
执行的——但它应该在完成 get_status
任务之前完成。
【参考方案1】:
该错误被归档为https://github.com/python/asyncio/issues/258 敬请期待。
作为快速解决方法,我建议使用自定义执行器,例如
loop = asyncio.get_event_loop()
executor = concurrent.futures.ThreadPoolExecutor(5)
loop.set_default_executor(executor)
在完成你的程序之前,请做
executor.shutdown(wait=True)
loop.close()
【讨论】:
很棒的安德鲁,感谢您的帮助。我没有意识到我正在和团队的一部分交谈:)。在 GH 上关注这个Changed in version 3.5.3: BaseEventLoop.run_in_executor() no longer configures the max_workers of the thread pool executor it creates
安德鲁,你能建议不是“快速解决方法”而是一些针对 Python 3.5 的强大解决方法吗?【参考方案2】:
你说得对,loop.getaddrinfo 使用ThreadPoolExecutor
在线程中运行socket.getaddrinfo
。
您正在使用带有超时的asyncio.wait_for,这意味着res = yield from asyncio.wait_for...
将在4 秒后引发asyncio.TimeoutError
。然后get_status
协程返回None
并且循环停止。如果作业在此之后完成,它将尝试在事件循环中安排回调并引发异常,因为它已经关闭。
【讨论】:
啊,这是有道理的,但这是我发现实现请求超时的唯一方法。你知道我可以在不关闭循环的情况下超时吗? @PatrickAllen 您可能希望增加默认为 5 的 number of workers。 @PatrickAllen 或者在关闭循环之前使用loop._default_executor.shutdown(wait=True)
。
我会将其标记为已回答,因为这似乎已经解决了最初的问题。我应该限制最大连接数吗?请求似乎无缘无故超时。也许我太快地提出了太多请求?
@PatrickAllen 好吧,5 个工作线程和一千个请求意味着您试图在 4 秒内运行 200 个socket.getaddrinfo
,这对我来说似乎是合理的,即使可以增加工作人员的数量。您还可以为request
提供自定义TcpConnector
以指定连接超时:connector=aiohttp.TCPConnector(loop=loop, force_close=True, conn_timeout=1)
以上是关于Asyncio RuntimeError:事件循环已关闭的主要内容,如果未能解决你的问题,请参考以下文章
Flask asyncio aiohttp - RuntimeError:线程'Thread-2'中没有当前事件循环
corroutine RuntimeError中的Asyncio:没有正在运行的事件循环
Python Asyncio 错误:“OSError:[WinError 6] 句柄无效”和“RuntimeError:事件循环已关闭”[重复]