asyncio运行或run_until_complete

时间:2019-04-09 10:12:12

标签: python python-asyncio

我以一种非常基本的方式将asyncio用于一个应用程序。查看互联网上的大多数教程(甚至是官方文档),我发现它们使用get_event_loop()loop.run_until_complete()

import asyncio

async def say(what, when):
    await asyncio.sleep(when)
    print(what)

loop = asyncio.get_event_loop()
loop.run_until_complete(say('hello world', 1))
loop.close()

但是在Python 3.7 docs中,我们可以看到:

  

应用程序开发人员通常应使用高级异步功能,例如asyncio.run(),并且几乎不需要引用循环对象或调用其方法。本部分主要面向需要更好地控制事件循环行为的较低级代码,库和框架的作者。

我发现它更加简洁易用,但仅适用于Python 3.7+。因此,在这里我必须做出选择,是使用Python 3.7+和run()还是使其与Python 3.6兼容并使用事件循环。您将如何处理?有没有简单的方法可以使其与Python 3.6向后兼容?在Python 3.7成为通用版本之前,我是否应该先检查Python版本并基于此使用一种方法或另一种方法?

2 个答案:

答案 0 :(得分:2)

可以通过复制asyncio.runners.py中的代码来复制asyncio.run。下面的一个来自Python 3.8。

from asyncio import coroutines, events, tasks


def run(main, *, debug=False):
    """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)
        loop.set_debug(debug)
        return loop.run_until_complete(main)
    finally:
        try:
            _cancel_all_tasks(loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
        finally:
            events.set_event_loop(None)
            loop.close()


def _cancel_all_tasks(loop):
    to_cancel = tasks.all_tasks(loop)
    if not to_cancel:
        return

    for task in to_cancel:
        task.cancel()

    loop.run_until_complete(
        tasks.gather(*to_cancel, loop=loop, return_exceptions=True))

    for task in to_cancel:
        if task.cancelled():
            continue
        if task.exception() is not None:
            loop.call_exception_handler({
                'message': 'unhandled exception during asyncio.run() shutdown',
                'exception': task.exception(),
                'task': task,
            })

答案 1 :(得分:1)

  

是否有一种简单的方法可以使[使用asyncio.run的代码]向后兼容Python 3.6?

您可以实现asyncio.run的简单替代,并在较旧的Python版本上调用它:

def run(aw):
    if sys.version_info >= (3, 7):
        return asyncio.run(aw)

    # Emulate asyncio.run() on older versions
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
        return loop.run_until_complete(aw)
    finally:
        loop.close()
        asyncio.set_event_loop(None)

与仅使用loop.run_until_complete()相比,此方法的优势在于,即使在较旧的Python版本上,您也可以执行新asyncio.run的语义的代码。 (例如,您将始终在新创建的事件循环上运行。)删除对3.7之前的Python的支持就像删除run填充程序并直接调用asyncio.run一样容易。