【问题标题】:asyncio run or run_until_complete异步运行或 run_until_complete
【发布时间】:2019-04-09 10:12:12
【问题描述】:

我以非常基本的方式将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 版本并基于此使用一种或另一种方式?

【问题讨论】:

  • 如果您要编写适用于两个版本的更复杂的代码适用于较新版本的更简单的代码,然后您将切换动态地在它们之间进行...不是更容易坚持使用同时适用于两者的更复杂的吗?
  • @deceze 是的,也许这是最好的选择,我想对此发表意见,并且在使其兼容的情况下,知道哪种方法是最好的方法
  • @deceze 在较旧的 Python 版本上模拟 asyncio.run 并不难,而且您可以获得在 asyncio.run 设置的条件下测试代码的优势,即在新创建的事件循环上。

标签: python python-asyncio


【解决方案1】:

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

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

import asyncio, sys, types

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

    # Emulate asyncio.run() on older versions

    # asyncio.run() requires a coroutine, so require it here as well
    if not isinstance(coro, types.CoroutineType):
        raise TypeError("run() requires a coroutine object")

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
        return loop.run_until_complete(coro)
    finally:
        loop.close()
        asyncio.set_event_loop(None)

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

【讨论】:

  • 不错!这看起来很棒!我会试试这个:) 谢谢!
  • 只是代码中的一个小问题,条件应该是sys.version_info >= (3, 7)。谢谢您的帮助! :)
  • 超级!非常感谢!
  • 怀疑finally块中2行的顺序不应该与Python标准库中的相反(这个问题的另一个答案)
  • @LouisMaddox 我认为这并不重要,但如果有疑问,请随意颠倒顺序
【解决方案2】:

可以通过从 asyncio.runners.py 复制代码来复制 asyncio.run。下面是from 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,
            })

【讨论】:

  • version 3.9 中,他们在shutdown_asyncgens 之后添加了loop.run_until_complete(loop.shutdown_default_executor())(否则相同),3.10 中没有变化
猜你喜欢
  • 2022-01-19
  • 2012-04-15
  • 1970-01-01
  • 1970-01-01
  • 2021-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多