異步編程技術

異步編程技術

這裏將介紹不一樣的異步編程實現。react

做爲程序員,咱們都面臨着一個問題,就是如何不讓咱們的程序阻塞。不管咱們是桌面開發,移動開發,甚至服務端開發。
有不少不一樣的實現來解決這個問題,包括:程序員

-Threading
-Callbacks
-Futures, Promises
-Reactive Extensions
-Coroutinesweb

咱們先簡明的看下前四種實現方式。編程

Threading

到目前爲止,線程是最爲程序員所知的一種避免阻塞應用的實現。api

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線程。這是一個很是常見的技術,可是它有一系列的缺點:promise

-線程很耗資源,它們要切換上下文
-線程數量有限,操做系統能啓動的線程數量是有限的,對於服務端來講,這是一個很大的瓶頸
-線程並不老是可用的,好比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
}

這個看起來相對優雅的解決了問題,可是一樣有幾個問題:異步

-對於多重嵌套回調,這將是一個很是難理解的方式
-錯誤處理變得很複雜,對於多重嵌套,錯誤的傳遞和處理變得很複雜async

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#引入 asyncawait 作爲語法的一部分。而對於Kotlin,這些只是庫函數。