如何在不使用等待的情况下拆分长协程?

时间:2018-06-20 18:27:51

标签: python python-3.x python-asyncio

我的协程太大了,我想将其拆分以提高可读性。

async def handle_message(self, message):
    message_type = message.get('type')

    if message_type == 'broadcast':
        ...
        for n in self._neighbors:
            await self.send_message(n, message)

    elif message_type == 'graph':
        ...

我想将处理广播消息的部分提取为私有方法,如下所示:

async def handle_message(self, message):
    message_type = message.get('type')
    ...

    if message_type = 'broadcast':
        await self._handle_broadcast(message)
    elif message_type = 'graph':
        ...

问题在于,这会改变代码的行为,因为_handle_broadcast部分是协程,并且由于我用await对其进行调用,所以它的执行可能会延迟。

如何确保协程立即运行并且不会延迟?

1 个答案:

答案 0 :(得分:2)

简而言之:使用await完全按照您的开始拆分协程。

  

问题在于,这会改变代码的行为,因为_handle_broadcast部分是协程,并且由于我用await对其进行调用,所以它的执行可能会延迟。

无论好坏,此前提是错误的。给定协程后,await将立即开始执行,而不会出现中间延迟。只有如果,协程调用了导致其暂停的内容(例如asyncio.sleep或尚无数据的网络读取),协程就会随之暂停-正是如果代码保持内联,您将得到什么。

从这个意义上讲,await <some coroutine>的工作方式类似于常规函数调用的协程,等效地可以精确地进行所需的非语义更改重构。这可以用一个例子来证明:

import asyncio

async def heartbeat():
    while True:
        print('tick')
        await asyncio.sleep(1)

async def noop():
    pass

async def coro():
    # despite "await", this blocks the event loop!
    while True:
        await noop()

loop = asyncio.get_event_loop()
loop.create_task(heartbeat())
loop.create_task(coro())
loop.run_forever()

上面的代码阻止了事件循环-即使coro在循环中除了await之外什么也不做。因此await不能保证屈服于事件循环,协程必须使用其他方法来做到这一点。 (此行为也可能是bugs的来源。)

在上述情况下,可以通过插入await asyncio.sleep(0)来获得事件循环“未卡住”。但是,在生产异步代码中,绝对不需要这种事情,在该代码中,应该对程序进行结构化,以使每个协程执行相对较少的工作,然后使用await获得更多数据。