非同步程式設計相關技術實現
非同步程式設計技術
這裡將介紹不同的非同步程式設計實現。
作為程式設計師,我們都面臨著一個問題,就是如何不讓我們的程式阻塞。無論我們是桌面開發,移動開發,甚至服務端開發。
有很多不同的實現來解決這個問題,包括:
-Threading
-Callbacks
-Futures, Promises
-Reactive Extensions
-Coroutines
我們先簡明的看下前四種實現方式。
Threading
到目前為止,執行緒是最為程式設計師所知的一種避免阻塞應用的實現。
fun postItem(item: Item) { val token = preparePost() val post = submitPost(token, item) processPost(post) } fun preparePost(): Token { // makes a request and consequently blocks the main thread return token }
我們假設preparePost
方法是一個很耗時的方法,這樣的話,它將會阻塞使用者互動。我們可以把這個方法放在一個單獨的執行緒中,這樣就避免了阻塞UI執行緒。這是一個非常常見的技術,但是它有一系列的缺點:
-執行緒很耗資源,它們要切換上下文
-執行緒數量有限,作業系統能啟動的執行緒數量是有限的,對於服務端來說,這是一個很大的瓶頸
-執行緒並不總是可用的,比如JavaScript就不支援執行緒
-編寫好的程式碼並不容易,容易出現各種競爭條件,會陷入併發程式設計苦海
Callbacks
簡單來說,就是把函式作為引數傳遞給另一個函式,一旦本身函式執行完成,就執行傳參的函式。
fun postItem(item: Item) { preparePostAsync { token -> submitPostAsync(token, item) { post -> processPost(post) } } } fun preparePostAsync(callback: (Token) -> Unit) { // make request and return immediately // arrange callback to be invoked later }
這個看起來相對優雅的解決了問題,但是同樣有幾個問題:
-對於多重巢狀回撥,這將是一個非常難理解的方式
-錯誤處理變得很複雜,對於多重巢狀,錯誤的傳遞和處理變得很複雜
Callbacks在事件迴圈結構的語言中比較常見,比如JavaScript,但更多的碼農傾向於其它的解決方案,比如promises 或者reactive extensions .
Futures, Promises
Futures,Promises,不同的平臺或語言有不同的叫法,它是承若將在某個點返回一個叫Promise 的物件,簡略操作如下所示:
fun postItem(item: Item) { preparePostAsync() .thenCompose { token -> submitPostAsync(token, item) } .thenAccept { post -> processPost(post) } } fun preparePostAsync(): Promise<Token> { // makes request an returns a promise that is completed later return promise }
這個方式對於我們來說,有一點挑戰,比如說:
-不同的程式設計模型。和callback類似,從上而下的鏈式回撥。傳統的程式設計結構,比如迴圈,錯誤處理都不在有效
-不同的平臺有不同的API
-具體的返回型別,返回型別並不是我們實際的資料結構
-錯誤處理變得複雜。錯誤的傳遞處理並不清晰明確。
Reactive Extensions
響應式擴充套件(Rx)被Eik Meijer 引入C#,雖然它確實存在於.NET平臺,但直到被Netflix 移植到Java平臺,才逐漸開始成為主流。從這起,各個平臺實現自己的響應式擴充套件,包括JavaScript(RxJS)。
Rx的背後思想是把資料當作流 來處理,並且該流 可以被觀測。實際上,Rx只是觀察者模式,帶有一系列的擴充套件來操作資料。
Rx和Futures很類似,但不同的是,我們可以認為Future返回一個直接的元素,而Rx返回流。另一方面,它提供了一中新的程式設計模型,就像所宣傳的口號那樣:
everything is a stream, and it's observable
這意味著有不同的解決方法,並且提供了不同的思路去寫非同步程式碼。相比於Futures不同的是,Rx被移植到多個平臺,我們有著一致的API體驗。包括C#,Java,JavaScript,或者其它實現了Rx的語言。
此外,Rx引入了更友好的錯誤處理方式。
Coroutines
Kotlin非同步程式設計的方式是使用coroutines,這是一種可掛起的執行方式,它可以在某一點掛起,之後從這點恢復繼續執行。
對於碼農來說,使用協程方法編寫非同步程式碼和編寫同步程式碼沒有什麼不同,這種編碼模型並不存在什麼挑戰。
舉個栗子:
fun postItem(item: Item) { launch { val token = preparePost() val post = submitPost(token, item) processPost(post) } } suspend fun preparePost(): Token { // makes a request and suspends the coroutine return suspendCoroutine { /* ... */ } }
這段程式碼將會執行耗時工作,但並不阻塞主執行緒。帶有suspend
修飾的preparePost
方法就是被成為可掛起函式。就像上面描述的那樣,它可以在某個點掛起,然後從該點恢復執行。
-函式簽名保持了一致,僅增加了suspend
修飾符。返回值也是我們想要的型別
-編寫程式碼和我們以前一樣,並不需要特殊的語法
-程式設計模型和API保持了一樣,我們可以繼續使用迴圈,異常處理,不需要學習新的api集
-它是平臺無關的,無論是執行在JVM,JavaScript平臺,還是其它,我們寫法都是一樣的,編譯器負責適配各個平臺。
Coroutines並不是Kotlin發明的新概念,它們已經存在了幾十年了,並且在其它語言非常流行,比如Go語言。需要注意的是,雖然Kotlin實現了Coroutines,但大多數功能都是在庫函式裡邊。事實上,除了suspend
關鍵詞,沒有其它的關鍵詞被引入到語言中。這個和C#有著較大的不同,C#引入async
和await
做為語法的一部分。而對於Kotlin,這些只是庫函式。