具有选择性return_exceptions的asyncio.gather

时间:2018-02-17 12:23:42

标签: python-asyncio

我希望asyncio.gather立即引发任何异常,除了某些特定的异常类,应该在结果列表中返回。现在,我只是稍微修改了CPython中asyncio.gather的规范实现并使用它,但我想知道是否有更规范的方法来实现它。

1 个答案:

答案 0 :(得分:2)

您可以使用功能更强大的asyncio.wait原语及其return_when=asyncio.FIRST_EXCEPTION选项来实现此类语义:

async def xgather(*coros, allowed_exc):
    results = {}
    pending = futures = list(map(asyncio.ensure_future, coros))
    while pending:
        done, pending = await asyncio.wait(
            pending, return_when=asyncio.FIRST_EXCEPTION)
        for fut in done:
            try:
                results[fut] = fut.result()
            except allowed_exc as e:
                results[fut] = e
    return [results[fut] for fut in futures]

想法是调用wait,直到所有期货都完成或观察到异常。该异常依次存储或传播,具体取决于它是否与allowed_exc匹配。如果已成功收集所有结果和允许的例外,则会按正确的顺序返回,与asyncio.gather一样。

修改asyncio.gather实现的方法可能很容易在较新的Python版本上失败,因为代码访问Future个对象的私有属性。另外,像uvloop这样的替代事件循环可以提高gatherwait的效率,这会根据公共API自动使xgather受益。

测试代码:

import asyncio

async def fail():
    1/0

async def main():
    print(await xgather(asyncio.sleep(1), fail(), allowed_exc=OSError))

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

运行时,代码立即引发,预期ZeroDivisionError与允许的OSError异常不匹配。将OSError更改为ZeroDivisionError会导致代码暂停1秒钟并输出[None, ZeroDivisionError('division by zero',)]