当使用asyncio.run()时,Asyncio事件循环会关闭。

10

我开始接触AsyncIO和AioHTTP,写一些基础代码以熟悉语法。我尝试了下面的代码,应该可以同时执行3个请求:

import time
import logging
import asyncio
import aiohttp
import json
from aiohttp import ClientSession, ClientResponseError
from aiocfscrape import CloudflareScraper

async def nested(url):
    async with CloudflareScraper() as session:
        async with session.get(url) as resp:
            return await resp.text()

async def main():
    URL = "https://www.binance.com/api/v3/exchangeInfo"
    await asyncio.gather(nested(URL), nested(URL), nested(URL))

asyncio.run(main())

这是输出结果:

raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

我不明白为什么会出现这个错误,有人能帮我吗?


你在哪个操作系统上运行代码? - user4815162342
1
看起来你遇到了这个问题。但很可能你的代码实际上是正常工作的,尽管出现了不美观的消息 - 你只是从main()返回内容而没有打印它,所以除了消息之外你没有任何输出。 - user4815162342
很遗憾,你不能这样做。我无法弄清如何使用try-except来抑制单个RuntimeError,无论捕获块放在哪里。 - jupiterbjy
@jupiterbjy: "不,它来自于asyncio.ProactorEventLoop相关的内部asyncio组件,处理Windows的IO事件循环" - 有趣的是,这条评论让人觉得问题可能出在aiohttp中,或者至少可以在那里修复。 - user4815162342
1
这是Windows的已知问题,请查看https://github.com/encode/httpx/issues/914。您可以通过设置事件循环策略来解决此问题。 - Greg
显示剩余5条评论
2个回答

11

更新

最初我推荐下面Greg的答案:

import asyncio
import sys

if sys.platform:
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

结果表明,使用 WindowsSelectorEventLoop 存在以下功能问题

  • 无法支持超过 512 个套接字
  • 无法使用管道
  • 无法使用子进程

这是因为 Windows 使用 I/O 完成端口,而不像 *nix - 因此 SelectorEventLoop 不适用于 Windows,也没有完全实现。

如果这些限制对您很重要 - 您可能最好使用本答案中的冗长解决方法。

请查看有关差异的更多信息,请访问文档

或者,考虑使用 Trio 而不是 asyncio,它更加稳定和一致。

import trio


async def task():
    await trio.sleep(5)


trio.run(task) 

原始帖子

我终于找到了如何保持 ProactorEventLoop 运行,防止 IO 关闭失败。

真的不知道为什么 Windows 的事件循环这么有问题,因为这也会发生在 asyncio.open_connectionasyncio.start_server 上。

为了解决这个问题,你需要在 forever 循环中运行事件循环,并手动关闭。

下面的代码将覆盖 Windows 和其他环境。

import asyncio
from aiocfscrape import CloudflareScraper


async def nested(url):
    async with CloudflareScraper() as session:
        async with session.get(url) as resp:
            return await resp.text()


async def main():
    await nested("https://www.binance.com/api/v3/exchangeInfo")


try:
    assert isinstance(loop := asyncio.new_event_loop(), asyncio.ProactorEventLoop)
    # No ProactorEventLoop is in asyncio on other OS, will raise AttributeError in that case.

except (AssertionError, AttributeError):
    asyncio.run(main())
    
else:
    async def proactor_wrap(loop_: asyncio.ProactorEventLoop, fut: asyncio.coroutines):
        await fut
        loop_.stop()

    loop.create_task(proactor_wrap(loop, main()))
    loop.run_forever()

这段代码将检查新的 EventLoop 是否是 ProactorEventLoop
如果是,就让循环一直运行,直到 proactor_wrap 等待 main 并安排停止循环。

否则 - 可能是所有非 Windows 操作系统 - 不需要这些额外的步骤,只需调用 asyncio.run() 即可。

像 Pycharm 这样的 IDE 会抱怨将 AbstractEventLoop 传递给 ProactorEventLoop 参数,可以放心忽略。


1
我尝试了这个,实际上没有出现错误。我希望这个答案能够得到足够的关注,因为我看到有很多关于同样问题的提问。谢谢! - JayK23
1
很高兴能够帮到你。多么讽刺啊,我在我的代码库中找了几个月都无法解决这个问题 - 是时候实现这个解决方法来看看它是否也适用于套接字服务器了。 - jupiterbjy
这是一个有趣的解决方案,我很喜欢它。然而,为什么不直接设置策略,而不是手动关闭事件循环呢? - Greg
甚至不知道有这个选项 - 阅读文档后发现“自 Python 3.8 起,ProactorEventLoop 是 Windows 的默认选项。” 不确定为什么这个有问题的使用 IOCP 的循环是默认的,但我也担心违背开发人员的决定。 - jupiterbjy
惊讶地发现它运行流畅,感谢提示! - jupiterbjy
显示剩余2条评论

8

虽然已有答案并被接受,但你可以用一行代码解决这个问题:asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

Event loop is closed是Windows上已知的一个问题(见https://github.com/encode/httpx/issues/914)。我猜想这个问题将在Python的后续版本中得到解决。为了避免错误,只需将事件循环策略设置为WindowsSelectorEventLoopPolicy即可。

如果你计划在非Windows环境下运行代码,则需要添加if语句来防止出现错误。例如:if sys.platform == 'win32'。或者添加代码来设置策略。

工作示例:

import asyncio
from aiocfscrape import CloudflareScraper
import sys

async def nested(url):
    async with CloudflareScraper() as session:
        async with session.get(url) as resp:
            print(resp.status)
            return await resp.text()

async def main():
    URL = "https://www.binance.com/api/v3/exchangeInfo"
    await asyncio.gather(nested(URL), nested(URL), nested(URL))

# Only preform check if your code will run on non-windows environments.
if sys.platform == 'win32':
    # Set the policy to prevent "Event loop is closed" error on Windows - https://github.com/encode/httpx/issues/914
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

asyncio.run(main())

1
注意!尽管这个答案可能隐藏了异常,但它会降低功能性:https://stackoverflow.com/questions/67964463/ - T.Todua
@T.Todua 很好的发现,我没有注意到这个问题 - 我会把它添加到我的答案顶部,希望人们能看到这些限制。在 WindowsSelectorEventLoop 上无法使用子进程对于某些人来说是一个相当大的问题。 - jupiterbjy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接