Python 协程

5

我在Javascript中有一点Promise的经验。我对Python非常熟悉,但是对协程还很新手,并且有一点我不明白:异步何时开始?

让我们考虑以下最简化的示例:

async def gen():
    await something
    return 42

据我所了解,await something会将我们函数的执行暂停,让主程序运行其他部分。在某个时刻,something有了一个新结果,之后gen也会很快有结果。
如果gensomething都是协程,那么根据互联网的智慧,它们都是生成器。而要知道生成器何时有新项可用,唯一的方法是轮询:x=gen(); next(x)。但这是阻塞的!调度程序如何“知道”x何时有结果?答案不能是“当something有结果时”,因为something也必须是生成器(因为它是协程)。这个论点递归地适用。
我无法摆脱这样一个想法:在某个时刻,进程只能同步等待。
1个回答

6
这里的秘密武器是asyncio模块。你的something对象必须本身就是一个可等待对象,要么依赖于更多可等待对象,要么必须从Future对象中产生yield。例如,asyncio.sleep()协程会产生一个Future
@coroutine
def sleep(delay, result=None, *, loop=None):
    """Coroutine that completes after a given time (in seconds)."""
    if delay == 0:
        yield
        return result

    if loop is None:
        loop = events.get_event_loop()
    future = loop.create_future()
    h = future._loop.call_later(delay,
                                futures._set_result_unless_cancelled,
                                future, result)
    try:
        return (yield from future)
    finally:
        h.cancel()

这里的语法使用了旧的生成器语法,以保持向后兼容旧版Python 3。

请注意,future不使用awaityield from;它们只是使用yield self直到满足某些条件。在上面的async.sleep()协程中,当产生结果时(在上面的async.sleep()代码中,通过futures._set_result_unless_cancelled()函数调用延迟后),就会满足该条件。

然后,事件循环将继续从它管理的每个挂起的future中获取下一个“结果”(有效地轮询它们),直到未来发出完成信号(通过引发包含结果的StopIteration异常;例如,从协程中return就会这样做)。此时,产生该future的协程可以被通知继续(通过发送future结果或如果未来引发除StopIteration之外的任何异常,则抛出异常)。

因此,以您的示例为例,循环将启动您的gen()协程,然后await something(直接或间接)产生一个future。该future将被轮询,直到引发StopIteration(表示它已完成)或引发其他异常为止。如果future已完成,则执行coroutine.send(result),使其能够继续前进到return 42行,触发具有该值的新StopIteration异常,从而允许等待gen()的调用协程继续等待。

所以你的意思是,协程不总是生成器,而是可以产生Future(假设我们选择使用asyncio)?而这个Future是在递归结尾处,知道如何与异步框架连接起来?所以生成器协程实际上根本不是异步的? - Paul
@Paul:协程实际上只是知道如何处理抛出的异常的生成器,语法更加优美。确保协程真正合作并允许在异步上下文中使用始终取决于开发人员。如果您编写一个仅阻塞的async def协程,那么这是您自己的问题。 :-) - Martijn Pieters
@Paul:这对于任何语言中的协程框架都是正确的。Java没有asyncawait语法,但有Futures。C#和JS具有相同的协程特性,并且也基于futures和事件循环的异步协作的整个前提。 - Martijn Pieters
1
我的误解是我认为 asyncio 负责在协程需要等待时不阻塞,但实际上这是协程的责任。此外,我没有意识到协程最终需要回到事件循环以保持异步。我总是把示例代码中的 asyncio.sleep 行视为纯粹用于演示目的,但错过了它实际上与事件循环紧密相关的重点。现在我明白了。我想。 - Paul
1
@Paul:记住,coroutine 中的 co 代表 cooperative。协程最终负责与其他协程合作。与线程随意切换不同,您在适当的时候切换协程。 - Martijn Pieters
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接