一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(六)初遇掛起

1. 引言

本文主要是通過比較實(shí)用的掛起函數(shù)joinawait來接觸實(shí)踐協(xié)程的掛起作用凿掂,同時(shí)本部分將會(huì)有較多的理解內(nèi)容涣觉。

2. 等待協(xié)程執(zhí)行完成

不多說,直接上代碼烘豌!

某啟動(dòng)一個(gè)協(xié)程并將job對(duì)象保存下來:

viewBinding.launchBtn -> {
    "Clicked launchBtn".let {
        myLog(it)
    }
    job?.cancel()
    job = scope.launch(Dispatchers.IO) {
        "Coroutine IO runs (from launchBtn)".let {
            myLog(it)
        }
        Thread.sleep(FIVE_SECONDS)
        "Coroutine IO runs after thread sleep (from launchBtn)".let {
            myLog(it)
        }
    }
}

然后另外一個(gè)地方,等待這個(gè)協(xié)程的執(zhí)行結(jié)束看彼,這里關(guān)鍵是join函數(shù)廊佩!

viewBinding.joinBtn -> {
    "Clicked joinBtn".let {
        myLog(it)
    }
    scope.launch(Dispatchers.Main) {
        "Coroutine Main runs (from joinBtn)".let {
            myLog(it)
        }
        val jobNonNull = job ?: throw IllegalStateException("No job launched yet!")
        jobNonNull.join()
        "Coroutine Main runs after join() (from joinBtn)".let {
            myLog(it)
        }
    }
}

這樣的話,先點(diǎn)擊launchBtn后在5秒內(nèi)點(diǎn)擊joinBtn靖榕,請(qǐng)問下面這兩行l(wèi)og标锄,輸出的順序會(huì)是?

"Coroutine IO runs after thread sleep (from launchBtn)"
"Coroutine Main runs after join() (from joinBtn)"

事實(shí)上茁计,這兩行的log的輸出順序料皇,必然是先第一行再第二行!

這便是由于掛起函數(shù)join的作用產(chǎn)生的效果星压!

掛起函數(shù)join的作用:掛起調(diào)用處所在的協(xié)程直到調(diào)用者協(xié)程執(zhí)行完成践剂。

3. 協(xié)程與線程等待完成函數(shù)的對(duì)照

協(xié)程中Job的join函數(shù)與線程Thread的join函數(shù)在功能設(shè)計(jì)上其實(shí)是類似的。

線程/協(xié)程對(duì)象的join函數(shù)調(diào)用后娜膘,將在調(diào)用處等待線程/協(xié)程對(duì)象執(zhí)行完成后再繼續(xù)往下執(zhí)行逊脯。

好像比較籠統(tǒng)或不好理解?那么來個(gè)詳細(xì)對(duì)比版吧:

在線程A執(zhí)行過程中調(diào)用了線程B的join函數(shù)竣贪,那么線程A進(jìn)入阻塞狀態(tài)(BLOCKED)军洼,直到線程B執(zhí)行完成后再轉(zhuǎn)化為可執(zhí)行狀態(tài)(RUNNABLE)巩螃,線程A在獲得CPU時(shí)間片后再繼續(xù)往下執(zhí)行。

在協(xié)程C執(zhí)行過程中調(diào)用了協(xié)程D的join函數(shù)匕争,那么協(xié)程C進(jìn)入掛起狀態(tài)(SUSPENDED)牺六,直到協(xié)程D執(zhí)行完成后再轉(zhuǎn)換為恢復(fù)狀態(tài)(RESUMED),協(xié)程C在獲得調(diào)度器的調(diào)度后再繼續(xù)往下執(zhí)行汗捡。

這里盡量簡(jiǎn)潔了淑际,如果還是看不懂?……那就……多看幾遍扇住?如果還是不懂春缕?…………罷了罷了,不懂的話艘蹋,建議先記下吧锄贼。

4. 關(guān)于掛起不得不提的點(diǎn)

說到協(xié)程的掛起,必要強(qiáng)調(diào)以下的核心內(nèi)容:

1) 操作系統(tǒng)層面沒有協(xié)程的存在女阀;

2) 協(xié)程的掛起狀態(tài)不對(duì)應(yīng)任何的線程狀態(tài)宅荤;

3) 協(xié)程處于掛起狀態(tài)之時(shí),不占用或阻塞任何線程浸策;

4) 如果用的是runBlocking方式啟動(dòng)協(xié)程冯键,上面的第2和第3點(diǎn)將不再成立;

對(duì)于第2和第3點(diǎn)庸汗,這便是協(xié)程掛起的神奇之處惫确!

掛起函數(shù)的調(diào)用,雖然在邏輯上是依次執(zhí)行的蚯舱,但是從操作系統(tǒng)執(zhí)行字節(jié)碼角度來看改化,掛起函數(shù)的執(zhí)行過程卻會(huì)是異步回調(diào)式的執(zhí)行邏輯。

點(diǎn)到即止枉昏,這部分是協(xié)程掛起中非常核心的內(nèi)容:CPS轉(zhuǎn)換和狀態(tài)機(jī)陈肛,有興趣的可以拓展深入探究或?qū)W習(xí)。

這里是基礎(chǔ)學(xué)習(xí)篇……

“哼兄裂,虧你還知道是基礎(chǔ)學(xué)習(xí)篇句旱,還放出這么多理解的內(nèi)容不是想勸退?”

“對(duì)不起咯懦窘,實(shí)在沒忍住前翎,見諒見諒稚配〕┩浚”

個(gè)人覺得,說到協(xié)程的掛起道川,這些內(nèi)容還是必須要提的午衰,理解好不理解也罷立宜,起碼得有個(gè)印象,協(xié)程的掛起畢竟是非常核心且關(guān)鍵的內(nèi)容臊岸。

5. 獲得協(xié)程的執(zhí)行結(jié)果返回

應(yīng)該都知道橙数,launch方式啟動(dòng)的協(xié)程沒有帶有返回值,而async方式啟動(dòng)的協(xié)程可以帶有返回值帅戒。

可能有不知道的小伙伴灯帮?我不管,反正你現(xiàn)在知道了逻住。

或許有小伙伴經(jīng)不住會(huì)問钟哥,"啥玩意?launch函數(shù)不是明明有返回值Job嗎瞎访?為啥說沒有返回值呢腻贰?“

好吧,這部分其實(shí)是函數(shù)式編程設(shè)計(jì)的內(nèi)容扒秸,我說的是協(xié)程帶有返回值播演,說的是協(xié)程執(zhí)行體(一般寫法會(huì)是lambda表達(dá)式的函數(shù)體部分)的返回值,而不是launch函數(shù)的返回值。

如果這個(gè)沒搞懂袍镀,建議先學(xué)習(xí)了解下Kotlin的函數(shù)類型挽荡、lambda表達(dá)式等函數(shù)式編程設(shè)計(jì)內(nèi)容。

…………怎么感覺不大對(duì)顶霞?隱約間又說道別的內(nèi)容了?好吧锣吼,沒忍住选浑。


趕緊上代碼!

先是通過async啟動(dòng)協(xié)程部分:

viewBinding.asyncBtn -> {
    "Clicked asyncBtn".let {
        myLog(it)
    }
    deferred?.cancel()
    deferred = scope.async(Dispatchers.IO) {
        val stringBuilder = StringBuilder()
        "Coroutine IO runs (from asyncBtn)".let {
            myLog(it)
        }
        Thread.sleep(FIVE_SECONDS)
        "TeaC".apply {
            "Coroutine IO runs after thread sleep: $this (from asyncBtn)".let {
                myLog(it)
            }
        }
    }
}

再是通過掛起函數(shù)await獲取所啟動(dòng)協(xié)程的返回值部分:

viewBinding.awaitBtn -> {
    "Clicked awaitBtn".let {
        myLog(it)
    }
    scope.launch(Dispatchers.Main) {
        "Coroutine Main runs (from awaitBtn)".let {
            myLog(it)
        }
        val deferredNonNull =
            deferred ?: throw IllegalStateException("No deferred async yet!")
        val ret = deferredNonNull.await()
        "Coroutine Main runs after await(): $ret (from awaitBtn)".let {
            myLog(it)
        }
    }
}

同樣的玄叠,先點(diǎn)擊asyncBtn然后5秒內(nèi)點(diǎn)擊awaitBtn古徒,那么下面兩行的日志輸出將會(huì)始終保證順序:

"Coroutine IO runs after thread sleep: $this (from asyncBtn)"
"Coroutine Main runs after await(): TeaC (from awaitBtn)"

join不同的是,await是有返回值的读恃,注意關(guān)鍵代碼:

val ret = deferredNonNull.await()

上述代碼隧膘,這里ret將會(huì)是async啟動(dòng)的協(xié)程函數(shù)體里的返回值,當(dāng)前實(shí)踐代碼中寺惫,類型是String疹吃,值為"TeaC"。

協(xié)程函數(shù)體的返回值西雀?協(xié)程函數(shù)體里沒看到有返回值的返回叭弧?好吧艇肴,這里搞清楚一個(gè)點(diǎn)腔呜,async后的花括號(hào)部分其實(shí)是lambda表達(dá)式叁温,而lambda表達(dá)式函數(shù)體部分的返回值會(huì)是最后一個(gè)表達(dá)式的返回值,可以有顯式的return關(guān)鍵字方式核畴,但是Kotlin開發(fā)文檔中并不建議顯式寫出return這種方式……

好像有點(diǎn)不對(duì)膝但?打住打住谤草!這部分其實(shí)是Kotlin函數(shù)式編程內(nèi)容跟束,所以…………

回到上述代碼,其實(shí)便是通過掛起函數(shù)await丑孩,獲得了async所啟動(dòng)的協(xié)程函數(shù)體中的返回值泳炉。如目標(biāo)協(xié)程還未結(jié)束時(shí),將掛起等待最終結(jié)果的返回嚎杨。

6. 兩種協(xié)程啟動(dòng)方式的對(duì)比

兩種協(xié)程啟動(dòng)方式花鹅,分別指的是launch和async啟動(dòng)協(xié)程的方式對(duì)比。

更具體地說枫浙,應(yīng)該是(launch/Job/join)和(async/Deferred/await)這兩個(gè)組合拳之間的對(duì)比刨肃。

  • launch函數(shù)的返回值是Job,而async函數(shù)的返回值是Deferred<T>箩帚;
  • launch啟動(dòng)的協(xié)程函數(shù)體的返回值必然是Unit真友,而async啟動(dòng)的協(xié)程函數(shù)體的返回值將是最后一個(gè)表達(dá)式的值;
  • Job#join()和Deferred#await()均是掛起函數(shù)紧帕,都有掛起協(xié)程等待協(xié)程執(zhí)行完成的作用盔然,但是前者沒有返回值(又或說返回值是Unit),后者有返回值是嗜,返回值將是async的協(xié)程函數(shù)體中的返回值愈案;

事實(shí)上,兩者對(duì)比上的差異遠(yuǎn)不止上述內(nèi)容鹅搪,比如在協(xié)程不同條件下的取消表現(xiàn)站绪,關(guān)于join/await總結(jié)如下:

對(duì)于join函數(shù)在各種場(chǎng)景下的總結(jié):

1)協(xié)程B中調(diào)用了協(xié)程A的join函數(shù)后,協(xié)程B等待到協(xié)程A完成后才繼續(xù)往下執(zhí)行丽柿;

2)協(xié)程B在等待協(xié)程A完成的過程中恢准,協(xié)程掛起,但協(xié)程B所執(zhí)行在的線程并沒有阻塞甫题;

3)協(xié)程B在調(diào)用協(xié)程A的join函數(shù)前馁筐,協(xié)程A已經(jīng)完成,則join函數(shù)被調(diào)用不會(huì)產(chǎn)生實(shí)際性效果且會(huì)繼續(xù)下執(zhí)行坠非;

4)協(xié)程B在掛起等待協(xié)程A的過程中敏沉,如果協(xié)程A被取消,則協(xié)程B的掛起狀態(tài)結(jié)束且繼續(xù)正常往下執(zhí)行;

5)協(xié)程B在掛起等待協(xié)程A的過程中赦抖,如果協(xié)程B被取消舱卡,則協(xié)程B在調(diào)用join函數(shù)之處會(huì)拋出CancellationException辅肾;

對(duì)于await函數(shù)在各種場(chǎng)景下的總結(jié):

1)協(xié)程B中調(diào)用了協(xié)程A的await函數(shù)后队萤,協(xié)程B等待到協(xié)程A完成并返回結(jié)果后才繼續(xù)往下執(zhí)行;

2)協(xié)程B在等待協(xié)程A結(jié)果的過程中矫钓,協(xié)程掛起要尔,但協(xié)程B所執(zhí)行在的線程并沒有阻塞;

3)協(xié)程B在調(diào)用協(xié)程A的await函數(shù)前新娜,協(xié)程A已經(jīng)完成并返回結(jié)果赵辕,則await函數(shù)直接返回協(xié)程A的執(zhí)行結(jié)果且往下繼續(xù)執(zhí)行;

4)協(xié)程B在掛起等待協(xié)程A結(jié)果的過程中概龄,如果協(xié)程A被取消还惠,則協(xié)程B在調(diào)用協(xié)程A的await方法處拋出CancellationException;

5)協(xié)程B在掛起等待協(xié)程A結(jié)果的過程中私杜,如果協(xié)程B被取消蚕键,則協(xié)程B在調(diào)用協(xié)程A的await方法處會(huì)拋出CancellationException;

不用擔(dān)心異常CancellationException的拋出衰粹,在協(xié)程函數(shù)體和掛起函數(shù)執(zhí)行中锣光,異常CancellationException是用作協(xié)程取消協(xié)作點(diǎn)用的,前文的取消篇內(nèi)容所用的ensureActive函數(shù)的真正取消協(xié)作點(diǎn)也是拋出此種異常铝耻。

注:完整的實(shí)踐代碼中誊爹,也提供了協(xié)程取消的寫法,根據(jù)已有的代碼作進(jìn)一步修改瓢捉,可以實(shí)踐驗(yàn)證上面的總結(jié)频丘。

7. 樣例工程代碼

代碼樣例Demo,見Github:https://github.com/TeaCChen/CoroutineStudy

本文示例代碼泡态,如覺奇怪或啰嗦椎镣,其實(shí)為CancelStepTwoActivity.kt中的代碼摘取主要部分說明,在demo代碼當(dāng)中兽赁,為提升細(xì)節(jié)內(nèi)容状答,有更加多的封裝和輸出內(nèi)容。

本文的頁面截圖示例如下:

image-6-1.png

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(一)協(xié)程啟動(dòng)

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(二)線程切換

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(三)初遇協(xié)程取消

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(四)協(xié)程作用域

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(五)再遇協(xié)程取消

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(六)初識(shí)掛起(本文)

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(七)初識(shí)結(jié)構(gòu)化

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(八)初識(shí)協(xié)程異常

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(九)異常與supervisor

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載刀崖,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者惊科。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市亮钦,隨后出現(xiàn)的幾起案子馆截,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜡娶,死亡現(xiàn)場(chǎng)離奇詭異混卵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)窖张,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門幕随,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宿接,你說我怎么就攤上這事赘淮。” “怎么了睦霎?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵梢卸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我副女,道長(zhǎng)蛤高,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任碑幅,我火速辦了婚禮戴陡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枕赵。我一直安慰自己猜欺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布拷窜。 她就那樣靜靜地躺著开皿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪篮昧。 梳的紋絲不亂的頭發(fā)上赋荆,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音懊昨,去河邊找鬼窄潭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛酵颁,可吹牛的內(nèi)容都是我干的嫉你。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼躏惋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼幽污!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起簿姨,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤距误,失蹤者是張志新(化名)和其女友劉穎簸搞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體准潭,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趁俊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刑然。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寺擂。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闰集,靈堂內(nèi)的尸體忽然破棺而出沽讹,到底是詐尸還是另有隱情般卑,我是刑警寧澤武鲁,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站蝠检,受9級(jí)特大地震影響沐鼠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叹谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一饲梭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焰檩,春花似錦憔涉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衩侥,卻和暖如春国旷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茫死。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工跪但, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人峦萎。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓屡久,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親爱榔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子被环,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容