【kotlin】- 攜程基本使用

簡(jiǎn)介

隨著kotlin不斷普及神得,以其簡(jiǎn)潔的語(yǔ)法糖频轿,易擴(kuò)展,空安全痊银,汲取了不同語(yǔ)言的優(yōu)點(diǎn)等...越來(lái)越受到開發(fā)者的青睞抵蚊。剛?cè)?code>kotlin,除了和Java不一樣的語(yǔ)法讓人難以習(xí)慣外溯革,“攜程”和“泛型”更是讓開發(fā)者頭疼贞绳。接下來(lái)由我?guī)Т蠹伊私鈑otlin攜程基本使用。

其它文章

【kotlin】- delay函數(shù)實(shí)現(xiàn)原理
【kotlin】- 攜程的執(zhí)行流程
【kotlin】- 攜程的掛起和恢復(fù)

創(chuàng)建攜程

  • \color{blue}{kotlin中使用Thread}
    如果在kotlin使用Thread創(chuàng)建線程致稀,還在像Java那樣new一個(gè)Thread對(duì)象冈闭,似乎缺乏違和感。kotlin提供了直接創(chuàng)建線程的方法抖单。

    fun main() {
        thread(start = true,isDaemon = false){
            println("${treadName()}=====創(chuàng)建一個(gè)線程")
        }
    }
    

    輸出:

    Thread-0=====創(chuàng)建一個(gè)線程
    
    Process finished with exit code 0
    

    是不是比Java的方式要簡(jiǎn)便許多萎攒,isDaemon指定線程是否是守護(hù)線程,如果這里指定為true日志是打印不出來(lái)的喲矛绘,原因可以百度一下守護(hù)線程耍休。

  • \color{blue}{啟動(dòng)一個(gè)全局?jǐn)y程}

    fun main() {
        // CoroutineScope(英文翻譯:攜程范圍,即我們的攜程體)
        GlobalScope.launch (CoroutineName("指定攜程名字")){
            delay(1000)
            println("${Thread.currentThread().name}======全局?jǐn)y程~")
        }
    }
    

    很簡(jiǎn)單的一個(gè)例子(官方例子main最后調(diào)用了sleep延遲函數(shù))货矮,運(yùn)行main羊精,發(fā)現(xiàn)在控制臺(tái)并沒有打印協(xié)調(diào)體中的日志,輸出如下:

    Process finished with exit code 0
    

    使用官方例子

    fun main() {
        GlobalScope.launch (CoroutineName("指定攜程名字")){
            delay(1000)
            println("${Thread.currentThread().name}======全局?jǐn)y程~")
        }
        Thread.sleep(2000L)
        println("${Thread.currentThread().name}======我是最后的倔犟~")
    }
    

    運(yùn)行輸出如下:

    DefaultDispatcher-worker-1======全局?jǐn)y程~
    main======我是最后的倔犟~
    
    Process finished with exit code 0
    

    解釋

    從第打印圖可以看出囚玫,攜程創(chuàng)建了新的線程DefaultDispatcher-worker-1來(lái)執(zhí)行喧锦,不在主線程读规,所以全局?jǐn)y程體和全局?jǐn)y程體外的代碼是在不同線程中異步執(zhí)行的。
    全局?jǐn)y程創(chuàng)建的是守護(hù)線程燃少,而主線程不是掖桦,所以當(dāng)進(jìn)程中所有非守護(hù)線程執(zhí)行完,進(jìn)程就會(huì)退出供汛,守護(hù)進(jìn)程也將不復(fù)存在。這就是為什么上面例子不能執(zhí)行打印代碼的原因涌穆。

    守護(hù)線程是指為其他線程服務(wù)的線程怔昨。在JVM中,所有非守護(hù)線程都執(zhí)行完畢后宿稀,無(wú)論有沒有守護(hù)線程趁舀,虛擬機(jī)都會(huì)自動(dòng)退出。因此祝沸,JVM退出時(shí)矮烹,不必關(guān)心守護(hù)線程是否已結(jié)束

    kotlin攜程創(chuàng)建的線程對(duì)象是CoroutineScheduler中Worker內(nèi)部類,看一下這個(gè)內(nèi)部類的初始化罩锐。默認(rèn)就是守護(hù)線程奉狈。如果大家想要驗(yàn)證,可以使用jps打印當(dāng)前執(zhí)行的Java進(jìn)程涩惑,在用jstack查看進(jìn)程中相關(guān)線程的情況仁期。

    internal inner class Worker private constructor() : Thread() {
       init {isDaemon = true}
    }
    
  • \color{blue}{啟動(dòng)子攜程}

    // runBlocking協(xié)程構(gòu)建器將 main 函數(shù)轉(zhuǎn)換為協(xié)程
    fun main(): Unit = runBlocking {
        launch {
            delay(1000)
            println("${treadName()}======局部攜程~")
        }
    }
    

    launch是CoroutineScope的擴(kuò)展函數(shù),所以必須在攜程體內(nèi)才可以調(diào)用竭恬。輸出如下:

    main======局部攜程~
    
    Process finished with exit code 0
    

    runBlocking會(huì)阻塞當(dāng)前線程并且等待跛蛋,在所有已啟動(dòng)的子協(xié)程執(zhí)行完畢之前不會(huì)結(jié)束。所以launch啟動(dòng)就是runBlocking子攜程痊硕,因?yàn)閘aunch在runBlocking攜程作用域中赊级。在看一個(gè)例子:

    fun main(): Unit = runBlocking {
        GlobalScope.launch {
            delay(2000L)
            println("${treadName()}======全局?jǐn)y程")
        }
        // 如果沒有下面的代碼,上面代碼不會(huì)執(zhí)行
        launch {
            delay(1000L)
            println("${treadName()}======局部攜程")
        }
    }
    

    輸出如下:

    main======局部攜程
    
    Process finished with exit code 0
    

    解釋

    GlobalScope.launch啟動(dòng)是全局?jǐn)y程岔绸,會(huì)重新新建一個(gè)線程來(lái)執(zhí)行理逊,并不是runBlocking的子攜程。所以并不會(huì)等待GlobalScope.launch攜程體執(zhí)行完再退出進(jìn)程亭螟。

  • \color{blue}{coroutineScope聲明攜程作用域}

    suspend fun main() {
        // 聲明攜程作用域,掛起函數(shù),會(huì)釋放底層線程用于其他用途,創(chuàng)建一個(gè)協(xié)程作用域并且在所有已啟動(dòng)子協(xié)程執(zhí)行完畢之前不會(huì)結(jié)束
        coroutineScope {
            // 在該攜程作用域啟動(dòng)攜程
            launch {
                delay(3000L)
                println("${treadName()}======才開始學(xué)習(xí)coroutines")
            }
        }
        println("${treadName()}======最后的倔犟~")
    }
    

    這種方式啟動(dòng)的攜程作用域就在coroutineScope內(nèi)挡鞍。注意日志線程名字,輸出如下:

    DefaultDispatcher-worker-1======才開始學(xué)習(xí)coroutines
    DefaultDispatcher-worker-1======最后的倔犟~
    
    Process finished with exit code 0
    

    從日志發(fā)現(xiàn)预烙,main居然不上在主線程執(zhí)行的墨微,其實(shí)并不是這樣,反編譯kotlin代碼扁掸,發(fā)現(xiàn)main主入口代碼變成這樣了RunSuspendKt.runSuspend(new KotlinShareKt$$$main(var0))翘县。其實(shí)coroutineScope就是創(chuàng)建一個(gè)攜程環(huán)境最域。

    在看一個(gè)復(fù)雜的點(diǎn)的例子

    fun main() = runBlocking { 
        launch {
            delay(2000L)
            println("${treadName()}======Task from runBlocking")
        }
        coroutineScope { // 創(chuàng)建一個(gè)協(xié)程作用域
            launch {
                delay(1000L)
                println("${treadName()}======Task from nested launch")
            }
    
            delay(100L)
            println("${treadName()}======Task from coroutine scope") // 這一行會(huì)在內(nèi)嵌 launch 之前輸出
        }
        println("${treadName()}======scope is over")
    }
    

    輸出如下:

    main======Task from coroutine scope
    main======Task from nested launch
    main======scope is over
    main======Task from runBlocking
    
    Process finished with exit code 0
    

    解釋

    launch {...}執(zhí)行了掛起函數(shù)delay,而coroutineScope{...}可以看著也是一個(gè)子攜程體锈麸,調(diào)用掛起函數(shù)delay镀脂。而launch {...}和coroutineScope{...}后面的代碼誰(shuí)先執(zhí)行就要看launch中delay延遲的時(shí)間了。

  • \color{blue}{CoroutineScope構(gòu)建攜程}

    fun main() {
        val cs = CoroutineScope(Dispatchers.Default)
        cs.launch {  }
    }
    
  • \color{blue}{withContext在指定攜程上下文啟動(dòng)攜程}
    使用給定的協(xié)程上下文調(diào)用指定的掛起塊忘伞,掛起直到它完成薄翅,并返回結(jié)果

    fun main() = runBlocking {
        val result = withContext(Dispatchers.Default) {
            delay(3000)
            println("${treadName()}======1")
            30
        }
        println("${treadName()}======$result")
    }
    

    輸出如下:

    DefaultDispatcher-worker-1======1
    main======30
    
    Process finished with exit code 0
    
  • \color{blue}{Android在生命周期內(nèi)啟動(dòng)攜程}
    需要引入庫(kù)

    androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha02
    

    使用:

    lifecycleScope.launch {}
    
  • \color{blue}{攜程超時(shí)}
    在實(shí)踐中絕大多數(shù)取消一個(gè)協(xié)程的理由是它有可能超時(shí)。 當(dāng)你手動(dòng)追蹤一個(gè)相關(guān) Job的引用并啟動(dòng)了一個(gè)單獨(dú)的協(xié)程在延遲后取消追蹤氓奈,這里已經(jīng)準(zhǔn)備好使用 withTimeout 函數(shù)來(lái)做這件事翘魄。

    withTimeout(1300L) {
       repeat(1000) { i ->
           println("I'm sleeping $i ...")
           delay(500L)
       }
    }
    

    擴(kuò)展
    由于取消只是一個(gè)例外,所有的資源都使用常用的方法來(lái)關(guān)閉舀奶。 如果你需要做一些各類使用超時(shí)的特別的額外操作暑竟,可以使用類似 withTimeoutwithTimeoutOrNull 函數(shù),并把這些會(huì)超時(shí)的代碼包裝在 try {...} catch (e: TimeoutCancellationException) {...} 代碼塊中育勺,而 withTimeoutOrNull通過(guò)返回 null 來(lái)進(jìn)行超時(shí)操作但荤,從而替代拋出一個(gè)異常。

  • \color{blue}{組合掛起函數(shù)}

    suspend fun doSomethingUsefulOne(): Int {
        delay(1000L) // 假設(shè)我們?cè)谶@里做了一些有用的事
        return 13
    }
    suspend fun doSomethingUsefulTwo(): Int {
        delay(1000L) // 假設(shè)我們?cè)谶@里也做了一些有用的事
        return 29
    }
    
    • 默認(rèn)順序調(diào)用
      val time = measureTimeMillis {
          val one = doSomethingUsefulOne()
          val two = doSomethingUsefulTwo()
          println("The answer is ${one + two}")
      }
      println("Completed in $time ms")
      
    • async 并發(fā)
      val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
      }
      println("Completed in $time ms")
      
    • 惰性啟動(dòng)的 async
      可選的涧至,async可以通過(guò)將 start 參數(shù)設(shè)置為 CoroutineStart.LAZY而變?yōu)槎栊缘摹?在這個(gè)模式下腹躁,只有結(jié)果通過(guò) await獲取的時(shí)候協(xié)程才會(huì)啟動(dòng),或者在 Job 的 start`函數(shù)調(diào)用的時(shí)候南蓬。
      val time = measureTimeMillis {
         val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
         val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
         // 執(zhí)行一些計(jì)算
         one.start() // 啟動(dòng)第一個(gè)
         two.start() // 啟動(dòng)第二個(gè)
         println("The answer is ${one.await() + two.await()}")
      }
      println("Completed in $time ms")
      
  • \color{blue}{攜程join}
    依然例子先行:

    fun main() = runBlocking{
        val job = GlobalScope.launch { // 啟動(dòng)一個(gè)新協(xié)程并保持對(duì)這個(gè)作業(yè)的引用
            delay(1000L)
            println("World!")
        }
        println("Hello,")
        job.join() // 等待直到協(xié)程執(zhí)行結(jié)束
    }
    

    輸出如下:

    Hello,
    World!
    
    Process finished with exit code 0
    

    按照之前的講解潜慎,GlobalScope.launch啟動(dòng)的是全局?jǐn)y程,并不屬于runBlocking的子攜程蓖康,所以runBlocking不會(huì)等待該攜程執(zhí)行完畢再退出進(jìn)程铐炫,那為什么這里會(huì)等待呢,那這就是join函數(shù)的功勞蒜焊,join作用是掛起協(xié)程直到攜程執(zhí)行完成倒信。

  • \color{blue}{攜程取消}
    協(xié)程的取消是 協(xié)作 的。一段協(xié)程代碼必須協(xié)作才能被取消泳梆。 所有 kotlinx.coroutines 中的掛起函數(shù)都是 可被取消的 鳖悠。它們檢查協(xié)程的取消, 并在取消時(shí)拋出 CancellationException 然而优妙,如果協(xié)程正在執(zhí)行計(jì)算任務(wù)乘综,并且沒有檢查取消的話,那么它是不能被取消的套硼。

    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // 一個(gè)執(zhí)行計(jì)算的循環(huán)卡辰,只是為了占用 CPU
            // 每秒打印消息兩次
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // 等待一段時(shí)間
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // 取消一個(gè)作業(yè)并且等待它結(jié)束
    println("main: Now I can quit.")
    

    打印輸出并沒在控制臺(tái)上看到堆棧跟蹤信息的打印。這是因?yàn)樵诒蝗∠膮f(xié)程中 CancellationException 被認(rèn)為是協(xié)程執(zhí)行結(jié)束的正常原因

  • 在 finally 中釋放資源

    fun main() = runBlocking {
        val job = launch {
            try {
                repeat(1000) { i ->
                    println("job: I'm sleeping $i ...")
                    delay(500L)
                }
            } finally {
                println("job: I'm running finally")
            }
        }
        delay(1300L) // 延遲一段時(shí)間
        println("main: I'm tired of waiting!")
        job.cancelAndJoin() // 取消該作業(yè)并且等待它結(jié)束
        println("main: Now I can quit.")
    }
    
  • 運(yùn)行不能取消的代碼塊
    在前一個(gè)例子中任何嘗試在 finally 塊中調(diào)用掛起函數(shù)的行為都會(huì)拋出 CancellationException,因?yàn)檫@里持續(xù)運(yùn)行的代碼是可以被取消的九妈。通常反砌,這并不是一個(gè)問(wèn)題,所有良好的關(guān)閉操作(關(guān)閉一個(gè)文件萌朱、取消一個(gè)作業(yè)宴树、或是關(guān)閉任何一種通信通道)通常都是非阻塞的,并且不會(huì)調(diào)用任何掛起函數(shù)晶疼。然而酒贬,在真實(shí)的案例中,當(dāng)你需要掛起一個(gè)被取消的協(xié)程翠霍,你可以將相應(yīng)的代碼包裝在 withContext(NonCancellable) {……} 中同衣,并使用 'withContext'函數(shù)以及 NonCancellable上下文。

    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            withContext(NonCancellable) {
                println("job: I'm running finally")
                delay(1000L)
                println("job: And I've just delayed for 1 sec because I'm non-cancellable")
            }
        }
    }
    delay(1300L) // 延遲一段時(shí)間
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // 取消該作業(yè)并等待它結(jié)束
    println("main: Now I can quit.")
    

結(jié)束語(yǔ)

很多例子都是官網(wǎng)的壶运,只是加上一些自己的理解,這篇文章只是帶大家快速入門kotlin攜程使用浪秘,后面會(huì)逐步深入蒋情,講解攜程的實(shí)現(xiàn)原理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耸携,一起剝皮案震驚了整個(gè)濱河市棵癣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夺衍,老刑警劉巖狈谊,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異沟沙,居然都是意外死亡河劝,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門矛紫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赎瞎,“玉大人,你說(shuō)我怎么就攤上這事颊咬∥裆” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵喳篇,是天一觀的道長(zhǎng)敞临。 經(jīng)常有香客問(wèn)我,道長(zhǎng)麸澜,這世上最難降的妖魔是什么挺尿? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上票髓,老公的妹妹穿的比我還像新娘攀涵。我一直安慰自己,他們只是感情好洽沟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布以故。 她就那樣靜靜地躺著,像睡著了一般裆操。 火紅的嫁衣襯著肌膚如雪怒详。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天踪区,我揣著相機(jī)與錄音昆烁,去河邊找鬼。 笑死缎岗,一個(gè)胖子當(dāng)著我的面吹牛静尼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播传泊,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼鼠渺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了眷细?” 一聲冷哼從身側(cè)響起拦盹,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溪椎,沒想到半個(gè)月后普舆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡校读,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年沼侣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歉秫。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡华临,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出端考,到底是詐尸還是另有隱情雅潭,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布却特,位于F島的核電站扶供,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏裂明。R本人自食惡果不足惜椿浓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扳碍,春花似錦提岔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至夯巷,卻和暖如春赛惩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趁餐。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工喷兼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人后雷。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓季惯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親臀突。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勉抓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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