gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 17:44:33 26 4
gpt4 key购买 nike

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

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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com