摘要:原文鏈接原文作者今天譯者注年月日是版本的發(fā)布日,同時會對協(xié)程的結(jié)構(gòu)化并發(fā)做一些介紹。進一步的閱讀結(jié)構(gòu)化并發(fā)的概念背后有更多的哲學(xué)?,F(xiàn)代語言開始為我們提供一種以完全非結(jié)構(gòu)化啟動并發(fā)任務(wù)的方式,這玩意該結(jié)束了。
原文鏈接:Structured concurrency
原文作者:Roman Elizarov
今天(譯者注:18年9月12日) 是 kotlinx.coroutines 0.26.0 版本的發(fā)布日,同時會對 Kotlin 協(xié)程的「結(jié)構(gòu)化并發(fā)」做一些介紹。它不僅僅是一個功能改變——它標志著編程風(fēng)格的巨大改變,我寫這篇文章就是為了解釋這一點。
在 Kotlin 1.1 也就是 2017年初, 首次推出協(xié)程作為實驗性質(zhì)的特性開始,我們一直在努力向程序員解釋協(xié)程的概念,他們過去常常使用線程理解并發(fā),所以我們舉的例子和標語是"協(xié)程是輕量級線程"。
此外,我們的關(guān)鍵 api 被設(shè)計為類似于線程 api,以簡化學(xué)習(xí)曲線。這種類比在小規(guī)模例子中很適用,但是它不能幫助解釋協(xié)程編程風(fēng)格的轉(zhuǎn)變。
當(dāng)我們學(xué)習(xí)使用線程編程時,我們被告知線程是昂貴的資源,不應(yīng)該到處創(chuàng)建它們。一個優(yōu)雅的程序通常在啟動時創(chuàng)建一個線程池然后使用它們搞些事情。有些環(huán)境(尤其是 iOS)甚至說"不贊成使用線程"(即使所有的東西仍然在線程上運行)。它們提供了一個系統(tǒng)內(nèi)的隨時可用的線程池,其中包含可向其提交代碼的相應(yīng)隊列。
但是協(xié)程的情況不同。它可以非常方便地創(chuàng)建很多你需要的協(xié)程,因為它們非常廉價。讓我們看一下協(xié)程的幾個用例。
異步操作(Asynchronous operations)假設(shè)你正在寫一個前端 UI 應(yīng)用(移動端、web 端或桌面端——對于這個例子并不重要),并且需要向后端發(fā)送一個請求,以獲取一些數(shù)據(jù)并使用結(jié)果更新 UI 模型。我們最初推薦這樣寫:
fun requestSomeData() {
launch(UI) {
updateUI(performRequest())
}
}
這里,我們使用 launch(UI) 在 UI 上下文中啟動一個新的協(xié)程,調(diào)用performRequest 掛起函數(shù)對后端執(zhí)行異步調(diào)用,而不阻塞主 UI 線程,然后使用結(jié)果更新 UI。每個 requestSomeData 調(diào)用都創(chuàng)建自己的協(xié)程,這很好,不是嗎?它和 C# JS 和 GO 中的異步編程并沒有太大的不同。
但是這里有個問題。如果網(wǎng)絡(luò)或后端出現(xiàn)問題,這些異步操作可能需要很長時間才能完成。此外,這些操作通常在一些 UI 元素(比如窗口或頁面)的范圍內(nèi)執(zhí)行。如果一個操作花費的時間太長,通常用戶會關(guān)閉相應(yīng)的 UI 元素并執(zhí)行其他操作,或者更糟糕的是,重新打開這個 UI 并一次又一次地嘗試該操作。但是前面的操作仍然在后臺運行,當(dāng)用戶關(guān)閉相應(yīng)的 UI 元素時,我們需要某種機制來取消它。在 Kotlin 協(xié)程中,這導(dǎo)致我們推薦了一些非常棘手的設(shè)計模式,人們必須在代碼中遵循這些模式,以確保正確處理這種取消。此外,你必須是中記住指定適當(dāng)?shù)纳舷挛?,否則 updateUI 可能會被錯誤的線程調(diào)用,從而破壞 UI。這是容易出錯的。一個簡單的launch{ ... } 很容易寫出來,但是你不應(yīng)該寫成這樣。
在更哲學(xué)的層面上,很少像線程那樣"全局"地啟動協(xié)程。線程總是與應(yīng)用程序中的某個局部作用域相關(guān),這個局部作用域是一個生命周期有限的實體,比如 UI 元素。因此,對于結(jié)構(gòu)化并發(fā),我們現(xiàn)在要求在一個協(xié)程作用域中調(diào)用 launch,協(xié)程作用域是由你的生命周期有限的對象(如 UI 元素或它們相應(yīng)的視圖模型)實現(xiàn)的接口。你實現(xiàn)一次協(xié)程作用域后, 你會發(fā)現(xiàn),在你的 UI 類中有一個簡單的 launch{ … } ,然后你寫很多遍,變得極容易寫又正確:
fun requestSomeData() {
launch {
updateUI(performRequest())
}
}
注意,協(xié)程作用域的實現(xiàn)還為 UI 更新定義了適當(dāng)?shù)膮f(xié)程上下文。你可以在其文檔頁面上找到一個完整的協(xié)程作用域?qū)崿F(xiàn)示例。對于一些比較少見的情況,你需要一個全局協(xié)程,它的生命周期受整個應(yīng)用生命周期限制,我們現(xiàn)在提供了 GlobalScope (全局作用域)對象,因此以前全局協(xié)程的launch{ … } 變成了 GlobalScope.launch { … } ,協(xié)程的"全局"含義變得直觀了。
并行分解(Parallel decomposition)我已經(jīng)就 Kotlin 協(xié)程進行了多次 討論,,下面的示例代碼展示了如何并行加載兩個圖片并在稍后將它們組合起來——這是一個使用 Kotlin 協(xié)程并行分解工作的慣用示例:
suspend fun loadAndCombine(name1: String, name2: String): Image {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
return combineImages(deferred1.await(), deferred2.await())
}
不幸的是,這個例子在很多層面上都是錯誤的。掛起函數(shù)loadAndCombine 本身將從一個已經(jīng)啟動的執(zhí)行更大操作的協(xié)程內(nèi)部調(diào)用。如果這個操作被取消了呢?然后加載這兩個圖片仍然沒有收到影響。這不是我們想從可靠代碼中的得到的,特別是如果這些代碼是許多客戶端使用后端服務(wù)的一部分。
我們推薦的解決方案是寫成這樣async(conroutineContext){ … } ,以便在子協(xié)程中加載兩個圖片,當(dāng)父協(xié)程被取消時,子協(xié)程將被取消。
它仍然不完美。如果加載第一個圖片失敗,那么 deferred1.await() 將拋出相應(yīng)的異常,但是加載第二個圖片的第二個 async 協(xié)程仍然在后臺工作。解決這個問題就更復(fù)雜了。
我們在第二個用例中看到了同樣的問題。一個簡單的 async { … } 很容易寫,但是你不應(yīng)該寫成這樣。
使用結(jié)構(gòu)化并發(fā),async 協(xié)程構(gòu)建器就像 luanch 一樣,變成了協(xié)程作用域上的一個擴展。你不能再簡單的編寫 async{ … } ,你必須提供一個作用域。并行分解的一個恰當(dāng)?shù)睦邮牵?/p>
suspend fun loadAndCombine(name1: String, name2: String): Image =
coroutineScope {
val deferred1 = async { loadImage(name1) }
val deferred2 = async { loadImage(name2) }
combineImages(deferred1.await(), deferred2.await())
}
你必須將代碼封裝到 coroutineScope { ... } 塊中,這個塊為你的操作及其范圍建立了邊界。所有異步協(xié)程都成為這個范圍的子協(xié)程,如果該作用域因為異常導(dǎo)致失敗或被取消了,它所有的子協(xié)程也將被取消。
進一步的閱讀結(jié)構(gòu)化并發(fā)的概念背后有更多的哲學(xué)。我強烈推薦閱讀 “結(jié)構(gòu)化并發(fā)的注意事項,或:Go 語句的危害” ,它很好的對比了經(jīng)典的 goto-statement 和結(jié)構(gòu)化編程。
現(xiàn)代語言開始為我們提供一種以完全非結(jié)構(gòu)化啟動并發(fā)任務(wù)的方式,這玩意該結(jié)束了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/7361.html
摘要:讓我們探討一下如何確保你的工作脫離主線程運行并保證執(zhí)行。這確保在默認情況下,你的工作是同步運行的,并且在主線程之外運行。這是應(yīng)該脫離主線程運行的工作,但是,因為它與直接相關(guān),所以如果關(guān)閉應(yīng)用程序則不需要繼續(xù)。 原文地址:WorkManager Basics 原文作者:Lyla Fujiwara 譯文出自:掘金翻譯計劃 本文永久鏈接:github.com/xitu/gold-m… 譯者:Ri...
閱讀 2071·2021-11-11 16:54
閱讀 2140·2019-08-30 15:55
閱讀 3642·2019-08-30 15:54
閱讀 419·2019-08-30 15:44
閱讀 2258·2019-08-30 10:58
閱讀 457·2019-08-26 10:30
閱讀 3077·2019-08-23 14:46
閱讀 3248·2019-08-23 13:46