<p>这里有两个问题:一个是关于“在顶层”或更具体地说,在开发环境中等待协同程序。另一个是运行一个没有事件循环的协同程序。在</p>
<p>关于第一个问题,这在Python中当然是可能的,就像在Chrome Canary Dev Tools中一样-通过工具自己与事件循环的集成来处理它。实际上,ipython7.0及更高版本支持asyncia <a href="https://ipython.readthedocs.io/en/stable/interactive/autoawait.html" rel="nofollow noreferrer">natively</a>,您可以按照预期在顶层使用<code>await coro()</code>。在</p>
<p>关于第二个问题,在没有事件循环的情况下驱动一个协程是很容易的,但是它不是很有用。让我们来看看为什么。在</p>
<p>调用协程函数时,它返回一个协程对象。此对象通过调用其<code>send()</code>方法来启动和恢复。当协同程序决定<em>挂起</em>(因为它<code>await</code>是阻塞的东西),<code>send()</code>将返回。当协同程序决定<em>返回</em>时(因为它已经到达结尾或者因为它遇到了一个显式的<code>return</code>),它将引发一个<code>StopIteration</code>异常,并将<code>value</code>属性设置为返回值。考虑到这一点,一次合作路线的最小驱动程序可能如下所示:</p>
<pre><code>def drive(c):
while True:
try:
c.send(None)
except StopIteration as e:
return e.value
</code></pre>
<p>这对于简单的协同工作非常有效:</p>
^{pr2}$
<p>或者更复杂的一点:</p>
<pre><code>>>> async def plus(a, b):
... return a + b
...
>>> async def pi():
... val = await plus(3, 0.14)
... return val
...
>>> drive(pi())
3.14
</code></pre>
<p>但是仍然缺少一些东西——上面的协同程序都没有暂停执行。当一个协程暂停时,它允许其他协程运行,这使得事件循环能够(看起来)一次执行多个协程。例如,asyncio有一个<code>sleep()</code>协程,当等待时,它将在指定的时间段内暂停执行:</p>
<pre><code>async def wait(s):
await asyncio.sleep(1)
return s
>>> asyncio.run(wait("hello world"))
'hello world' # printed after a 1-second pause
</code></pre>
<p>但是,<code>drive</code>无法执行此协同程序以完成:</p>
<pre><code>>>> 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
</code></pre>
<p>实际情况是,<code>sleep()</code>通过生成一个特殊的“future”对象与事件循环通信。等待未来的合作路线只能在未来确定后才能恢复。“真正的”事件循环将通过运行其他协程来实现,直到未来完成。在</p>
<p>为了解决这个问题,我们可以编写自己的<code>sleep</code>实现,它与我们的小事件循环一起工作。为此,我们需要使用迭代器来实现waitable:</p>
<pre><code>class my_sleep:
def __init__(self, d):
self.d = d
def __await__(self):
yield 'sleep', self.d
</code></pre>
<p>我们生成一个元组,协同程序调用者看不到它,但它会告诉<code>drive</code>(我们的事件循环)该怎么做。<code>drive</code>和<code>wait</code>现在如下所示:</p>
<pre><code>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
</code></pre>
<p>使用此版本,<code>wait</code>可以正常工作:</p>
<pre><code>>>> drive(wait("hello world"))
'hello world'
</code></pre>
<p>这仍然不是很有用,因为驱动协同程序的唯一方法是调用<code>drive()</code>,它同样支持单个协同例程。因此,我们不妨编写一个同步函数,简单地调用<code>time.sleep()</code>并调用一天。为了支持异步编程的用例,<code>drive()</code>需要:</p>
<ul>
<li>支持多个协同路由的运行和暂停</li>
<li>在驱动器循环中实现新协同程序的生成</li>
<li>允许协同程序在IO相关事件上注册唤醒,例如文件描述符变得可读或可写—同时支持多个这样的事件而不损失性能</li>
</ul>
<p>这就是asyncio事件循环带来的结果,以及许多其他特性。davidbeazley在<a href="https://www.youtube.com/watch?v=MCs5OvhV9S4" rel="nofollow noreferrer">this talk</a>中出色地演示了从头开始构建事件循环,他在现场观众面前实现了一个功能性事件循环。在</p>