Skip to content

Instantly share code, notes, and snippets.

@uranusjr
Last active June 15, 2018 07:06
Show Gist options
  • Select an option

  • Save uranusjr/283df2ac9d2ee97500aff2f3e4df6b30 to your computer and use it in GitHub Desktop.

Select an option

Save uranusjr/283df2ac9d2ee97500aff2f3e4df6b30 to your computer and use it in GitHub Desktop.

Revisions

  1. uranusjr revised this gist May 17, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 沒有交集啊.md
    Original file line number Diff line number Diff line change
    @@ -123,7 +123,7 @@ loop.close()

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

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

    ```python
    # 無關程式省略。
  2. uranusjr revised this gist May 17, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 沒有交集啊.md
    Original file line number Diff line number Diff line change
    @@ -123,7 +123,7 @@ loop.close()

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

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

    ```python
    # 無關程式省略。
  3. uranusjr revised this gist May 17, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion 沒有交集啊.md
    Original file line number Diff line number Diff line change
    @@ -123,7 +123,7 @@ loop.close()

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

    從上面的程式可以看出來,`async_generator`*a generator that generates coroutine functions*。但它本身並不 async。一個 async generator,根據定義,應該在 generate 東西的時候是 async。講得好懂一點,如果你有複數個 async generators,它們應該要可以交錯著 generate 東西出來,而不是循序著被 exhaust。上面的程式只有用 generator 一次,所以看不出來,不過如果多用幾次:
    從上面的程式可以看出來,`async_generator`*a generator that generates coroutine functions*。但它本身並不 async。Async generator 的定義與**它 generate 出來的東西**無關,而是需要** generate 東西的時候** async。這個意思是,如果你有複數個 async generators,它們應該要可以交錯著 generate 東西出來,而不是循序著被 exhaust。上面的程式只有用 generator 一次,所以看不出來,不過如果多用幾次:

    ```python
    # 無關程式省略。
  4. uranusjr created this gist May 17, 2017.
    159 changes: 159 additions & 0 deletions 沒有交集啊.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,159 @@
    回應 <https://gist.github.com/rayshih/4144d6b8bc045fc26daf8887bd0cb4e2>

    ---

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

    ```python
    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` 來排程。它其實完全等同於下面的程式:

    ```python
    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 改寫前面的程式,就會像下面這樣:

    ```python
    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.sleep``count_down` 是 coroutines,所以對它們的 `yield from` 可以用 `await` 取代。

    從上面的程式可以看出來,`async_generator`*a generator that generates coroutine functions*。但它本身並不 async。一個 async generator,根據定義,應該在 generate 東西的時候是 async。講得好懂一點,如果你有複數個 async generators,它們應該要可以交錯著 generate 東西出來,而不是循序著被 exhaust。上面的程式只有用 generator 一次,所以看不出來,不過如果多用幾次:

    ```python
    # 無關程式省略。

    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 仍然是很新的概念,所以資源還是以影片居多。下面有一些我常推薦的概念性影片,或許會對你有幫助。有興趣可以看一看,然後更重要的,請**理解**

    * [Practical Python Async for Dummies]
    * [A. Jesse Jiryu Davis: What Is Async, How Does It Work, And When Should I Use It? - PyCon 2014]
    * [How Do Python Coroutines Work?]


    [Practical Python Async for Dummies]: https://www.youtube.com/watch?v=5_K8GwZ_268
    [A. Jesse Jiryu Davis: What Is Async, How Does It Work, And When Should I Use It? - PyCon 2014]: https://www.youtube.com/watch?v=9WV7juNmyE8
    [How Do Python Coroutines Work?]: https://www.youtube.com/watch?v=idLtMISlgy8