Bootstrap

关于 Asyncio,别再使用run_until_complete了

熟悉Python 的 Asyncio 进行异步编程的小伙伴肯定对下面的写法不陌生:

import asyncio

async def test():
    await asyncio.sleep(3)
    print("Test rersult")

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

这种写法也是在网上搜到的最多的协程运行方式,但从 Python3.7 版本开始,官方引入了一个新的、更简单的运行方式asincio.run,同样是实现上面代码的功能,我们就可以使用以下的方式:

async def test():
    await asyncio.sleep(3)
    print("Test rersult")

asyncio.run(test())

只需要一行即可,是不是更简单了?

下面我们直接从源码来看以下run()方法是如何实现的:

def run(main, *, debug=None):
    """Execute the coroutine and return the result.

    This function runs the passed coroutine, taking care of
    managing the asyncio event loop and finalizing asynchronous
    generators.

    This function cannot be called when another asyncio event loop is
    running in the same thread.

    If debug is True, the event loop will be run in debug mode.

    This function always creates a new event loop and closes it at the end.
    It should be used as a main entry point for asyncio programs, and should
    ideally only be called once.

    Example:

        async def main():
            await asyncio.sleep(1)
            print('hello')

        asyncio.run(main())
    """
    if events._get_running_loop() is not None:
        raise RuntimeError(
            "asyncio.run() cannot be called from a running event loop")

    if not coroutines.iscoroutine(main):
        raise ValueError("a coroutine was expected, got {!r}".format(main))

    loop = events.new_event_loop()
    try:
        events.set_event_loop(loop)
        if debug is not None:
            loop.set_debug(debug)
        return loop.run_until_complete(main)
    finally:
        try:
            _cancel_all_tasks(loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
            loop.run_until_complete(loop.shutdown_default_executor())
        finally:
            events.set_event_loop(None)
            loop.close()
  1. 先看注释Execute the coroutine,所以我们可以知道 main 需要是一个协程对象,而代码的第36、37 两行(源码中的行数,上文中正式代码的的第 4、5 行)也是对入餐 main 做了相应的校验,如果不是协程对象则会报错ValueError

  2. 注释:If debug is True, the event loop will be run in debug mode.是针对参数 debug 的,所以 debug 的类型应该是一个 bool 型,即 True or False,如果是 True 则会以 debug 模式运行。

  3. 正文的前三行,是检查当前环境中有没有已经在运行中的事件循环,如果有的话则报错。

  4. 经过前面5 行的相关检查验证后,开始了正式的逻辑处理代码。第一个 try 语句块中,我们看到了熟悉的内容,set_event_looprun_until_complete,所以 run()方法其实就是把我们熟悉的协程运行方式进一步的进行了包装,形成了更高层级的调用方法(也是官方推荐的调用方式)。

    Tips:针对debug 只做了不为 None 的判断,那么是不是我们可以把 debug 设置为任意值呢?答案是不可以,因为 set_debug()方法的实现逻辑在BaseEventLoop类中,而BaseEventLoop类继承自events.AbstractEventLoop,该类是一个抽象类,数据类型校验在events.pyi 文件中,在该文件中注明了 set_debug 方法的入参为 bool 类型。

  5. try 语句块中完成了协程代码的运行逻辑部分,那么finally语句块中的内容就是完成了注释中的closes it at the end了。在这部分代码中,一共包含了三个方法:

    • _cancel_all_tasks(loop):该方法中首先获取了所有的 task(task 是协程中一个重要的概念),将所有的 task 做了取消操作,并且再次将 task 运行run_until_complete(),保证执行完所有的任务。再对所有的任务做判断,有异常则抛出规范化的内容。
    • 在循环中运行loop.shutdown_asyncgens()方法,关闭所有的异步生成器,即对所有的生成器调用aclose()关闭方法。
    • shutdown_default_executor():关闭默认执行器。在 3.9 版本中新增了该方法,在以前的版本中只有shutdown_asyncgens()方法。该方法调用了线程的关闭程序来关闭执行器(默认为ThreadPoolExecutor),完善了多种协程的调用方式下的关闭释放动作。
  6. 最后一个finally语句块中,关闭事件循环

 

以上就是对asyncio.run()方法源码的解读,如有错误欢迎指出。同时官方也推荐使用该方法,大家用起来吧!

;