python - 如何在事件循环之外运行协程?

标签 python async-await python-asyncio

通常,您通过执行以下操作来获取协程的结果:

async def coro():
    await asycnio.sleep(3)
    return 'a value'

loop = asyncio.get_event_loop()
value = loop.run_until_complete(coro())

出于好奇,在不使用事件循环的情况下获取该值的最简单方法是什么?

[编辑]

我认为更简单的方法是:

async def coro():
    ...

value = asyncio.run(coro())  # Python 3.7+

但是有什么方法可以像 JS 那样全局地yield from(或await)coro() ?如果不是,为什么?

最佳答案

这里有两个问题:一个是关于“在顶层”或更具体地在开发环境中等待协程。另一个是关于在没有事件循环的情况下运行协程。

关于第一个问题,这在 Python 中当然是可能的,就像在 Chrome Canary Dev Tools 中是可能的一样 - 通过工具通过自身与事件循环的集成来处理它。事实上,IPython 7.0 及更高版本支持 asyncio natively并且您可以按预期在顶层使用 await coro()

关于第二个问题,没有事件循环驱动单个协程很容易,但是用处不大。让我们来看看原因。

当协程函数被调用时,它返回一个协程对象。该对象通过调用其 send() 方法启动和恢复。当协程决定暂停(因为它await是阻塞的东西)时,send() 将返回。当协程决定返回时(因为它已经到达终点或因为它遇到了明确的return),它会引发一个StopIteration异常value 属性设置为返回值。考虑到这一点,单个协程的最小驱动程序可能如下所示:

def drive(c):
    while True:
        try:
            c.send(None)
        except StopIteration as e:
            return e.value

这对于简单的协程非常有用:

>>> async def pi():
...     return 3.14
... 
>>> drive(pi())
3.14

或者更复杂一点的:

>>> async def plus(a, b):
...     return a + b
... 
>>> async def pi():
...     val = await plus(3, 0.14)
...     return val
... 
>>> drive(pi())
3.14

但是仍然缺少一些东西——上述协程都没有暂停它们的执行。当一个协程挂起时,它允许其他协程运行,这使得事件循环能够(看起来)一次执行许多协程。例如,asyncio 有一个 sleep() 协程,当等待时,它会在指定的时间段内暂停执行:

async def wait(s):
    await asyncio.sleep(1)
    return s

>>> asyncio.run(wait("hello world"))
'hello world'      # printed after a 1-second pause

但是,drive 无法执行此协程完成:

>>> drive(wait("hello world"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in drive
  File "<stdin>", line 2, in wait
  File "/usr/lib/python3.7/asyncio/tasks.py", line 564, in sleep
    return await future
RuntimeError: await wasn't used with future

发生的事情是 sleep() 通过产生一个特殊的“ future ”对象与事件循环通信。等待 future 的协程只能在未来设定后恢复。 “真正的”事件循环将通过运行其他协程来实现,直到 future 完成。

为了解决这个问题,我们可以编写自己的 sleep 实现,与我们的迷你事件循环一起使用。为此,我们需要使用迭代器来实现等待:

class my_sleep:
    def __init__(self, d):
        self.d = d
    def __await__(self):
        yield 'sleep', self.d

我们产生一个元组,协程调用者看不到它,但会告诉 drive(我们的事件循环)要做什么。 drivewait 现在看起来像这样:

def drive(c):
    while True:
        try:
            susp_val = c.send(None)
            if susp_val is not None and susp_val[0] == 'sleep':
                time.sleep(susp_val[1])
        except StopIteration as e:
            return e.value

async def wait(s):
    await my_sleep(1)
    return s

在这个版本中,wait 按预期工作:

>>> drive(wait("hello world"))
'hello world'

这仍然不是很有用,因为驱动协程的唯一方法是调用 drive(),它同样支持单个协程。所以我们不妨编写一个同步函数,简单地调用 time.sleep() 并调用它一天。为了让我们的协同程序支持异步编程的用例,drive() 需要:

  • 支持多个协程的运行和挂起
  • 在驱动循环中实现新协程的生成
  • 允许协程在 IO 相关事件上注册唤醒,例如文件描述符变为可读或可写 - 同时支持多个此类事件而不会损失性能

这就是 asyncio 事件循环以及许多其他功能带来的好处。 this talk 中出色地演示了从头开始构建事件循环。由 David Beazley 编写,他在现场观众面前实现了一个功能性事件循环。

关于python - 如何在事件循环之外运行协程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52783605/

相关文章:

python - 通过 Cron 作业的音频

python - 使用 aiohttp 无法完全下载图像

c# - 为 async void main 设置 ApartmentState

python - aiohttp中的代理请求

python - 超正方体未找到错误 : two docker container python app (docker-compose)

Python 3.2.1 : exec ('x = y()' ) sets a value in a toy example, 但不在完整代码中

android - 构建apk时cocos 2d-x-3.5错误: EOFError: EOF when reading a line

c# - 使用 async/await 运行多个无限循环

python-asyncio - 类型错误 : object NoneType can't be used in 'await' expression

python - Jupyter、asyncio 和 Slack