Skip to content

Instantly share code, notes, and snippets.

@uranusjr
Last active June 15, 2018 07:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save uranusjr/283df2ac9d2ee97500aff2f3e4df6b30 to your computer and use it in GitHub Desktop.
Save uranusjr/283df2ac9d2ee97500aff2f3e4df6b30 to your computer and use it in GitHub Desktop.

回應 https://gist.github.com/rayshih/4144d6b8bc045fc26daf8887bd0cb4e2


我一直覺得你的發言和其他人沒有交集,但想不通到底哪裡出了問題。但看了這段程式之後,我覺得有點懂了。原文的程式不容易看 timestamp(加上 1th 2th 3th 讓我豆頁痛),所以我稍微改寫如下。

import asyncio
import time

begin = time.time()

def async_generator():
    @asyncio.coroutine
    def count_down(i, c):
        print('Run {}, countdown {} (clock {:.5f})'.format(i, c, time.time() - begin))
        if c == 0:
            return
        yield from asyncio.sleep(0.5)
        yield from count_down(i, c - 1)

    i = 1
    while i <= 5:
        yield count_down(i, i)
        i += 1

@asyncio.coroutine
def run_all(asyncGen):
    for a in asyncGen:
        yield from a

loop = asyncio.get_event_loop()
loop.run_until_complete(run_all(async_generator()))
loop.close()

輸出:

Run 1, countdown 1 (clock 0.00040)
Run 1, countdown 0 (clock 0.50487)
Run 2, countdown 2 (clock 0.50493)
Run 2, countdown 1 (clock 1.00741)
Run 2, countdown 0 (clock 1.50859)
Run 3, countdown 3 (clock 1.50867)
Run 3, countdown 2 (clock 2.00978)
Run 3, countdown 1 (clock 2.51487)
Run 3, countdown 0 (clock 3.01876)
Run 4, countdown 4 (clock 3.01882)
Run 4, countdown 3 (clock 3.52049)
Run 4, countdown 2 (clock 4.02429)
Run 4, countdown 1 (clock 4.52681)
Run 4, countdown 0 (clock 5.02723)
Run 5, countdown 5 (clock 5.02733)
Run 5, countdown 4 (clock 5.52934)
Run 5, countdown 3 (clock 6.03095)
Run 5, countdown 2 (clock 6.53256)
Run 5, countdown 1 (clock 7.03788)
Run 5, countdown 0 (clock 7.54188)

這段程式透露的根本問題

或許上面這樣會比較容易看出來:在這段程式裡,所有的 coroutines 是被依序執行,沒有交錯。這整段程式根本是 synchrounous,只是用了 asyncio 來排程。它其實完全等同於下面的程式:

import time

begin = time.time()

def count_down(i):
    for c in range(i, -1, -1):
        print('Run {}, countdown {} (clock {:.5f})'.format(i, c, time.time() - begin))
        if c == 0:
            break
        time.sleep(0.5)
        yield

for i in range(1, 6):
    for _ in count_down(i):
        pass

我之前提過一個觀點:

Async program 不符合人類習慣思維,才會被說一開始的學習 overhead 很高,寫起來容易卡。尤其如果同步異步 paradigms 並立(例如 Python),就更容易在切換的時候出問題。

一般人習慣的就是 synchronous 寫法,所以即使用了 async API,仍然需要一直注意,才不會不小心寫出實質上根本不 async 的程式——而且完全不會注意到。這些問題需要親身經歷,而且即使經驗豐富的人,也免不了犯這個錯。甚至可以說,只有不知道自己有這個盲點的人,才會天真地相信 async 程式容易寫。

而且它根本沒有實作 Asynchronous Generator

如果用 async-await keywords 改寫前面的程式,就會像下面這樣:

import asyncio
import time

begin = time.time()

def async_generator():
    async def count_down(i, c):
        print('Run {}, countdown {} (clock {:.5f})'.format(i, c, time.time() - begin))
        if c == 0:
            return
        await asyncio.sleep(0.5)
        await count_down(i, c - 1)

    i = 1
    while i <= 5:
        yield count_down(i, i)
        i += 1

async def run_all(asyncGen):
    for a in asyncGen:
        await a

loop = asyncio.get_event_loop()
loop.run_until_complete(run_all(async_generator()))
loop.close()

首先,coroutine decorator 可以被 async def 關鍵字取代。接著 asyncio.sleepcount_down 是 coroutines,所以對它們的 yield from 可以用 await 取代。

從上面的程式可以看出來,async_generator 是一個 generator,然後它 generate 的東西(yield count_down(i, i))是 coroutine。它是 a generator that generates coroutines,但本身並不 async。Async generator 的定義與它 generate 出來的東西無關,而是需要在 generate 東西的時候是 async。這個意思是,如果你有複數個 async generators,它們應該要可以交錯著 generate 東西出來,而不是循序進行(一個跑完之後,才開始跑下一個)。上面的程式只有用 generator 一次,所以看不出來,不過如果多用幾次:

# 無關程式省略。

async def run_all(*gens):
    for g in gens:
        for a in g:
            await a

loop.run_until_complete(run_all(async_generator(), async_generator()))

就很明顯了。這個 generator function 產生的 generators 只能循序被 exhaust,所以不是 async generators。

之前那篇我就不回了

Mosky 叫我不要開嘲諷,我自己也覺得會讓人覺得嘲諷,可是該講的還是得講。

你根本不懂 asynchrony。而且你根本不知道自己不懂。

Syntax 不是 async programming 的重點,程式究竟怎麼動才是。我想不出更好的說法,但這不是在嗆你,是事實。就因為這樣,使得你沒抓到我要討論的點,所以我才會覺得這麼卡。我們討論的不是同一件事。你需要從頭想過 async 的目的,以及 coroutine 的原理。有了這些概念,才能知道一個程式為什麼要變得 async,以及應該如何變得 async。

抱歉我只對 Python 資源比較熟,而 async 在 Python 仍然是很新的概念,所以資源還是以影片居多。下面有一些我常推薦的概念性影片,或許會對你有幫助。有興趣可以看一看,然後更重要的,請理解

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment