## Re: `asyncio` 好寫嗎? 首先,其實還滿開心有機會可以做個小小辯論的啦。我的回應如下 > 這把好幾個主題混在一起了,需要一個一個看。 ### 1. Async Program 好寫嗎? > 不好寫。東西好不好寫的意思,代表它和 programmer 的內在思考模式符合程度。Async program 不符合人類習慣思維,所以才會被說一開始的學習 overhead 很高,寫起來容易卡。尤其如果同步與異步 paradigms 並立(例如 Python),就更容易在切換的時候出問題。 一開始學習的 overhead 很高不代表寫起來就會很卡,舉例來說不少人覺得 Rails 寫起來很順手,但大部分的人都說相對學習門檻比較高。我的確有一段時期不習慣 async program,但現在是完全沒有問題的。當然這只能代表我個人經驗,不過同時也是我要問的問題:你覺得 Async Program 不好寫,所以是哪裡不好寫?你的回答是「不符合人類習慣思維」。 ### 2. Async-await 好用嗎? > 看你跟誰比。正常人都比較習慣 synchronous programming,所以 async program 的寫法越 synchronous,感覺就會越好用。Function reference callback → Anonymous function callback → Promise API → Async-await 這整個系統本身就是在把 async program 寫得越來越像 synchronous,所以當然是越後面越好用。 其實我覺得真正的重點在於 composition,而不是看起來是不是 synchronous code。 async await 再怎麼樣都只是 syntax sugar 而已。 > 但上面只考慮 single-threaded non-blocking API。我自己的觀察是因為發展 promised-based system 的先鋒大多是 single-threaded (e.g. Lua 和 JavaScript),所以不會這樣比較,但以 Python 社群的角度,async-await 面對的比較對象是 multi-threading 與 multi-processing,而這兩個都比 coroutine 啊 promise 啊 event loop 這些東西容易理解得多,也就更容易上手。 實際上 Promise 只是其中比較有名的代表,使用物件來表達 async operation 的執行過程的,也不是只有只支援 single-threaded 的程式語言,舉例來說 RxJava,所以我並不認為 promise(或類似的,像 Future/Task) 只適用於 single thread 環境。因此我認為用 single-threaded、 multi-threading 來作為分水嶺並不恰當。 另外,multi-threading 與 multi-processing 在稍微複雜一點的環境下常常需要解決 lock 的問題,而使用 coroutine/promise/evet loop 等等,都傾向 immutable data,所以比較沒有 lock 的問題,在此為前提之下,我完全不認同 multi-thread/process 有比較好理解,而容易上手常常只是初學者的錯覺。當然如果是要深究到 fine-grain 資源管理的話,那當然還是得回到 multi-thread/process,不過我們在討論的是易用,而不是效能極限。 ### 3. `asyncio` 好用嗎? > 不好用。它的內部架構有些地方很繞(例如我就不懂為什麼 `Future` 與 `Task` 要是兩個不同的類型),介面也不 consistent(有時候只吃 coroutine 有時候只吃 future 大部分時候又都可以),不過更大的問題是對一般使用者而言暴露出太多底層。 不錯,這才是應該有的回答,明確點出 library 抽象滲漏問題 > 這和 `asyncio` 的設計目的有關。如果你去聽 [Guido 在 PyCon US 的演講](https://www.youtube.com/watch?v=aurOB4qYuFM),`asyncio`(那時候還叫 Tulip)的主要角色是為現存系統(例如 Twisted 和 Tornado)提供共同 event loop implementation。所以你讀 PEP 3156 時會發現很大一段篇幅在解釋每一層的 API,甚至還有專門一段在講 interoperability。這都顯示 `asyncio` 的主要 audience 不是 end users,而是把實作優秀 interface 的任務交給第三方套件——例如 Tornado。 ## Generator 夠了 > 夠什麼?Generator 有它能做和不能做的事。它是 `asyncio`(和檯面上所有 Python 的 async-await 函式庫)實作 Promise API 的基礎,所以你也可以用它取代很多 `asyncio` 的功能。但只用純 Python 的 generator 沒辦法為 Python 的所有功能實作 async variant,因為這會有雞生蛋生雞問題。最簡單的矛盾:如果你只用 generator 實作 async API,那要怎麼實作 async 的 generator? 其實沒有雞生蛋蛋生雞的問題, async API 以 generator 實作,並不代表不能實作 async 的 generator。假設 async API 的 type 是 `Promise`,而 generator object 的 type 是 `Generator`,那「async 的 generator」就的 type 就是 `Generator`。當然我還沒研究 Python 是不是有什麼歷史因素導致不好或甚至不能這樣做,但其他語言的實作你可以參考 Haskell 的 Monad Transformer 或是我之前貼的連結 ## JavaScript、Python、別的語言,論述不變 > 這沒有道理,因為每個語言(和內建函式庫)都有自己的特性,把一樣的東西架上去時,就需要考慮不一樣的 interoperability。Python 人和 C# 人重疊性很低,然後大部分人都不太熟 Lua(我也不熟),所以就用 JavaScript 當例子和 Python 比較吧。在 JavaScript 實作 Promise/A+ 與 Python 實作 `asyncio` 底層時的差異可以分成兩部分來討論: 同意,當然每個語言都有不同的特性,比較時的確需要考慮,這也是為什麼我要問這些問題。 ### 1. JavaScript 只有一個 event loop 實作,而且你看不到 > JavaScript 從語言初期就隱含一個 event loop,所有的 ecosystem 也都圍繞這個性質發展。Python 沒有,所以 Python 的函式庫有的不考慮 event loop,也有的實作自己的 event loop。前面講過,`asyncio` 的初始目的就是為了統一它們,但因為少了 JavaScript 的假設,實作與介面就必須非常 explicit,用起來就很卡。 如果我上面所說的,JavaScript 的 implicit event loop 並不能作為 Async Programming 特別好寫的理由。一樣拿 RxJava 為例子,在 Java 的環境下,雖然必須要 explicit 的描述 thread 的切換,但並不影響他使得 Async Programming 更容易撰寫的論述。 ### 2. JavaScrtipt 有執行緒限制、Python 沒有 > 因為 JavaScript 保證 one and only one event loop,async function 和 promise API、callback-style API 的對接非常乾淨。當你 await 你建立的 Promise 時,很明顯是要在那個 event loop 執行;在 `asyncio` 裡,同樣的事情就會變得非常繞,因為 Python 擁有完整的執行緒實作,且每個執行緒都可以擁有自己的 event loop,卻又沒有保證會有。 同上。 ### 舉個例子 > 如果我想用 `asyncio.sleep` 定時,每五秒印出一段字,就得這樣做: ```python import asyncio def run_once(previous_future=None): print('message') future = asyncio.ensure_future(asyncio.sleep(5)) future.add_done_callback(run_once) loop = asyncio.get_event_loop() loop.call_soon(run_once) loop.run_forever() ``` > 當然以延遲這個 case 而言,`asyncio` 有更方便的 `loop.call_later`,不過一般來說 coroutine 與 synchronous function 就得這樣接。我個人是覺得這樣已經有點煩了,不過注意一下 `ensure_future` 那行。前面有說 Python 可以擁有多個 event loops,那要怎麼知道是哪一個?這就是 `get_event_loop` 的用意;當程式初始化時,`asyncio` 會自動建立一個預設的 event loop;絕大部分 `asyncio` 關於 future 與 task 的 API 都吃一個 `loop` keyword argument,讓你可以指定要把它跑在哪個 event loop 上;如果不指定(像上面的狀況),就會用預設的。 > 這也是為什麼 `asyncio` 沒辦法像 ES6 一樣,async function 可以有透明的 promise API,能夠直接對接。Coroutine 沒有關於 event loop 的資訊,不知道自己要怎麼執行;你必須用它建立一個 task(通常用 `ensure_future`,至於為什麼名字是 future 建出來卻是 task 你問 Guido 吧我也很想知道),然後執行那個 task。 > 這裡面還有很多很多講不完的雷,不過上面應該夠證明 async 的實作標準不是所有語言通用了。 我看起來只是單純的環境設置問題,~~而且你這段程式碼看起來有改善的空間,我晚點來看看能不能簡化他。~~ anyway 如下 ```python import asyncio async def run_once(): print('message') await asyncio.sleep(5) await run_once() loop = asyncio.get_event_loop() loop.run_until_complete(run_once()) loop.close() ``` 說明一下:你上述的 code 沒用到 coroutine 的特性,跟直接用 sleep + callback 是差不多的。 ## 所以 `asyncio` 到底好不好 > Async programming 需要上手,但它是很有意義的工具,上手之後可以對付很多問題。 沒有什麼東西是不需要學習時間的。 > `asyncio` 不好用,但它的設計沒有大問題,只是需要更多更好的包裝,以及合適的輔助工具。 所以你的意思是他雖然沒有設計上大問題,但因為 API 並不適合給 end user 使用,所以不好用囉? > 官方的 `asyncio` 文件寫得很爛,但它其實不是給一般使用者看的;對一個完整的系統而言,文件本來也就不是最好的學習方法,與其把 `asyncio` 想成 Python 裡面的一個 module,更應該想成像 Django 這樣的完整 framework,需要 tutorial 與 cookbook,而目前還沒有足夠資源,所以學起來累。 > 如果你是一個 established user(但沒學過 concurrency 相關知識),想開始在你的程式裡加上一點這類功能,那麽 `asyncio` 不是好選擇。但如果你確定需求,且願意(希望)投資源下去,那麼 `asyncio` 是類似工具裡,最需要學習的一個。 > `asyncio` 是有點不好,但都不是根本的缺陷,可以在未來改進。但更重要的事,它是對你的應用不見得好。 如果不好用、不適合,能探究原因會比跟從 best practice 好。我認為這才是工程師能不能更上一層樓的關鍵。 ## Summary of this reply: 總結一下我同意的觀點: 1. Document 不夠清楚簡單、教學導向文件太少,所以不好上手 2. asyncio 設計上不要求簡化(Task/Future/coroutine 容易誤用問題) 跟我不同意的觀點: 1. async-await 在 python 以 multi thread/process 為主要思考面向 - 如同你說的,asyncio 試圖整合不同類型的 concurrent 環境,否為 multi thread/process 應該不是重點,況且除了 JS 以外也有非 single thread 而使用 async-await,所以這論述並沒有說服力。 2. generator 不夠用,所以需要 async syntax - 我想我附帶的參考文件應該就足夠說明,不過如果你需要範例的話,我應該可以花時間寫出你要的「async 的 generator」。 3. 「別的語言,論述不變」沒有道理 - 在 generator 或是 async 的觀點上,在現今眾多語言都支援 first-class function 甚至 async-await 語法糖支援,在我們提到的這些語言中,都沒有你所謂的「考慮不一樣的 interoperability」的疑慮。 以及可以再討論的: 1. Async Programming 不符合人類習慣思維,不好上手 - 真要說的話 ooxx paradigm programming language 本來就不是所有人的思考方式,我覺得這必須要有系統地做調查才能定論,不過這應該算是一種大型社會實驗。XD 補充一點,async-await 可以說是 asynchronous only generator,他們的重點都是如何用看起來「主動的」方式,來拿到原本只能「被動」取得的值,所以不管是哪種程式語言、是否為多執行緒、使用的封裝物件為 Promise/Task/Future 等等等等,都不影響他的抽象性質。 有什麼錯誤或是沒寫清楚的地方,再煩請你指教,謝謝 :)