怎样理解async-await

简易版event loop

利用python3.3引入的yield from语法,可以实现一个简易的event loop支持所谓的协程(coroutine):

import time
def sleep(seconds):  # coro.send(None => _)
    before = time.time()
    until = before + seconds
    after = yield until  # coro.send(now => $after)
    return after - before

def countdown(label, length, delay=1):  # coro.send(None => _)
    for i in range(length-1, -1, -1):
        delta = yield from sleep(delay)  # sleep.return => $delta
        print("label:%s count:%d delta:%.3f" % (label, i, delta))
    print(label, 'completed!')

class SleepingLoop:
    def __init__(self, *coros):
        self._coros = coros
        self._waiting = []

    def pop_min(self):
        index, (coro, wait_until) = min(enumerate(self._waiting), key=lambda x:x[1][1])
        self._waiting.pop(index)
        return coro, wait_until

    def run_until_complete(self):
        for coro in self._coros:
            wait_until = coro.send(None)  # coro => sleep.yield(until => $wait_until)
            self._waiting.append((coro, wait_until))

        while self._waiting:
            coro, wait_until = self.pop_min()
            now = time.time()
            if now < wait_until:
                time.sleep(wait_until - now)
                now = time.time()

            try:
                wait_until = coro.send(now)  # coro => sleep.yield(until => $wait_until)
                self._waiting.append((coro, wait_until))
            except StopIteration:
                pass


if __name__ == '__main__':
    loop = SleepingLoop(countdown('A', 5), countdown('B', 4, 1.4))
    loop.run_until_complete()

运行的结果如下:

label:A count:4 delta:1.006
label:B count:3 delta:1.402
label:A count:3 delta:1.006
label:B count:2 delta:1.406
label:A count:2 delta:1.001
label:A count:1 delta:1.001
label:B count:1 delta:1.403
label:A count:0 delta:1.003
A completed!
label:B count:0 delta:1.405
B completed!

引入async/await

如果生成器语法一会儿用作惰性产生数据,一会儿用作实现协程,会使程序的可读性变差。从python3.5开始,如果用@types.coroutine修饰一个生成器,那么这个生成器就可以用await来暂停执行和交换数据。同时如果一个生成器内部用了await,那么就要在def前面加上async。引入了这样的语法后,就很容易区分协程和普通生成器了。

使用async/await后,上面的例子可以改写为:

import time
import types

@types.coroutine
def sleep(seconds):  # coro.send(None => _)
    before = time.time()
    until = before + seconds
    after = yield until  # coro.send(now => $after)
    return after - before

async def countdown(label, length, delay=1):  # coro.send(None => _)
    for i in range(length-1, -1, -1):
        delta = await sleep(delay)  # sleep.return => $delta
        print("label:%s count:%d delta:%.3f" % (label, i, delta))
    print(label, 'completed!')

class SleepingLoop:
    def __init__(self, *coros):
        self._coros = coros
        self._waiting = []

    def pop_min(self):
        index, (coro, wait_until) = min(enumerate(self._waiting), key=lambda x:x[1][1])
        self._waiting.pop(index)
        return coro, wait_until

    def run_until_complete(self):
        for coro in self._coros:
            wait_until = coro.send(None)  # coro => sleep.yield(until => $wait_until)
            self._waiting.append((coro, wait_until))

        while self._waiting:
            coro, wait_until = self.pop_min()
            now = time.time()
            if now < wait_until:
                time.sleep(wait_until - now)
                now = time.time()

            try:
                wait_until = coro.send(now)  # coro => sleep.yield(until => $wait_until)
                self._waiting.append((coro, wait_until))
            except StopIteration:
                pass


if __name__ == '__main__':
    loop = SleepingLoop(countdown('A', 5), countdown('B', 4, 1.4))
    loop.run_until_complete()

参考