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