asyncio抛出RuntimeError,异常被忽略。

6
下面是一个简单的程序,用于收集URL长度。
import aiohttp
import asyncio
from time import perf_counter


URLS = ['http://www.cnn.com', 'http://www.huffpost.com', 'http://europe.wsj.com',
        'http://www.bbc.co.uk', 'http://failfailfail.com']

async def async_load_url(url, session):
    try:
        async with session.get(url) as resp:
            content = await resp.read()
        print(f"{url!r} is {len(content)} bytes")
    except IOError:
        print(f"failed to load {url}")        

async def main():

    async with aiohttp.ClientSession() as session:
        tasks = [async_load_url(url, session) for url in URLS]
        await asyncio.wait(tasks)

if __name__ == "__main__":
    start = perf_counter()
    asyncio.run(main())
    elapsed = perf_counter() - start
    print(f"\nTook {elapsed} seconds")


为什么以下代码在Python 3.9中会因为运行时错误并忽略异常而失败?如何修复?
回溯信息是:RuntimeError: Event loop is closed,具体来说是与Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001F8A7A713A0>相关。

这是运行时错误吗?还是警告?它可能希望您对asyncio.wait返回的任务执行某些操作。 - dirn
请更新问题以包括回溯信息。 - dirn
看起来你正在使用Windows,如果是这样,在aiohttp中这是一个已知的问题 https://github.com/aio-libs/aiohttp/issues/4324 - Matt Fowler
2个回答

6
这是由于aiohttp在Windows上已知问题导致的,详细信息请查看https://github.com/aio-libs/aiohttp/issues/4324。有几种方法可以解决这个错误。第一种方法是获取事件循环并调用run_until_complete而不是像这样调用asyncio.run(main())
asyncio.get_event_loop().run_until_complete(main())

或者在调用 asyncio.run(main()) 之前将事件循环策略更改为 WindowsSelectorEventLoopPolicy,这也可以解决问题,因为使用 WindowsProtractorEventLoopPolicy 时似乎会出现问题。

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())

当然,第二种解决方案会使您的代码针对特定平台,因此请注意。


-1
你正在使用 asyncio.await 运行所有任务,但是你没有检查已完成的任务是否有异常。 await 返回两个序列:一个包含已完成的任务,另一个包含待处理的任务 - 你需要查询已完成的任务是否有异常:
async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.task(async_load_url(url, session), name=url) for url in URLS]
        done, pending = await asyncio.wait(tasks)
        for task in done:
            try:
                task.exception() # in task execption is re-raised
            except Exception as exc:
                print (f"Exception loading url {task.name}:\n {exc}")

如果这是一个长时间的过程,并且您想在发生异常时处理它们,asyncio.wait 提供了一个接口来方便地处理 - 只需告诉它何时应该返回:

        
    async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.task(async_load_url(url, session), name=url) for url in URLS]
        while tasks:
            done, tasks = await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
            for task in done:
                try:
                    task.exception() # in task execption is re-raised
                except Exception as exc:
                    print (f"Exception loading url {task.name}:\n {exc}")


异常不是由任务引发的。它们在事件循环关闭时引发,并且当事件循环对象的引用计数降至零时被删除。可以说,这是 Protractor 循环 __del__ 实现中的一个错误,因为它在尝试执行任何剩余的回调之前没有验证它是否已经关闭,但无论如何进行任务异常检查都无法解决这个问题。 - Martijn Pieters

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