入門-協程(Coroutine)

使用協程需要引入

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

1.什么是協程

官方文檔(本質上,協程是輕量級的線程。)

import kotlinx.coroutines.*

fun main() {
    GlobalScope.launch { // 在后臺啟動一個新的協程并繼續(xù)
        delay(1000L) // 非阻塞的等待 1 秒鐘(默認時間單位是毫秒)
        println("World!") // 在延遲后打印輸出
    }
    println("Hello,") // 協程已在等待時主線程還在繼續(xù)
    Thread.sleep(2000L) // 阻塞主線程 2 秒鐘來保證 JVM 存活
}

個人理解:協程是一個線程框架布蔗,協程就是方法調用封裝成類線程的API强戴。

使用協程

啟動

協程需要運行在協程上下文環(huán)境歉摧,在非協程環(huán)境中憑空啟動協程燃异,有三種方式

runBlocking{}

啟動一個新協程礼饱,并阻塞當前線程作郭,直到其內部所有邏輯及子協程邏輯全部執(zhí)行完成哨查。

該方法的設計目的是讓suspend風格編寫的庫能夠在常規(guī)阻塞代碼中使用逗抑,常在main方法和測試中使用。

GlobalScope.launch{}

在應用范圍內啟動一個新協程寒亥,協程的生命周期與應用程序一致邮府。這樣啟動的協程并不能使線程保活溉奕,就像守護線程褂傀。

由于這樣啟動的協程存在啟動協程的組件已被銷毀但協程還存在的情況,極限情況下可能導致資源耗盡腐宋,因此并不推薦這樣啟動紊服,尤其是在客戶端這種需要頻繁創(chuàng)建銷毀組件的場景。

CoroutineScope + launch{}

這是在應用中最推薦使用的協程使用方式——為自己的組件實現CoroutieScope接口胸竞,在需要的地方使用launch{}方法啟動協程欺嗤。使得協程和該組件生命周期綁定,組件銷毀時卫枝,協程一并銷毀煎饼。從而實現安全可靠地協程調用。

在一個協程中啟動子協程校赤,一般來說有兩種方式

launch{}

異步啟動一個子協程

async{}

異步啟動一個子協程吆玖,并返回Deffer對象,可通過調用Deffer.await()方法等待該子協程執(zhí)行完成并獲取結果马篮,常用于并發(fā)執(zhí)行-同步等待的情況

下面舉個栗子

class Test CoroutineScope(): CoroutineScope {
   
    override fun getList(handler: Handler<Result<Response>>){
        launch{
            val deffer1 = async{ awaitResult<List<JsonObject>>{ dbService.getContentList(it) } }
            val deffer2 = async{ awaitResult<List<JsonObject>>{ dbService.getAuthorList(it) } }
            val contents = deffer1.await()
            val authors = deffer2.await()
            val reuslt = contents.map{ content -> 
                content.put("author", authors.filter{ ... }.first())
            }
            resultHandler.succeed(reuslt)
        }
    }
}

協程的取消

launch{}返回Job沾乘,async{}返回Deffer,Job和Deffer都有cancel()取消協程浑测。
取消自協程不影響父協程翅阵,取消父協程,子協程也取消迁央。

從協程內部看取消的效果

  • 標準庫的掛起方法會拋出CancellationException異常掷匠。
  • 用戶自定義的常規(guī)邏輯并不會收到影響,除非我們手動檢測isActive標志岖圈。
    一個栗子
val job = launch {
    // 如果這里不檢測isActive標記讹语,協程就不會被正常cancel,而是執(zhí)行直到正常結束
    while (isActive) { 
        ......
    }
}
job.cancelAndJoin()

學習了啟動跟取消蜂科,來看看協程異常顽决。

異常

Kotlin協程的異常有兩種

  • 因協程取消短条,協程內部suspend方法拋出的CancellationException
  • 常規(guī)異常,這類異常才菠,有兩種異常傳播機制
    • launch:將異常自動向父協程拋出慌烧,將會導致父協程退出
    • async: 將異常暴露給用戶(通過捕獲deffer.await()拋出的異常)

上一個官方栗子

fun main() = runBlocking {
    val job = GlobalScope.launch { // root coroutine with launch
        println("Throwing exception from launch")
        throw IndexOutOfBoundsException() // 我們將在控制臺打印 Thread.defaultUncaughtExceptionHandler
    }
    job.join()
    println("Joined failed job")
    val deferred = GlobalScope.async { // root coroutine with async
        println("Throwing exception from async")
        throw ArithmeticException() // 沒有打印任何東西,依賴用戶去調用等待
    }
    try {
        deferred.await()
        println("Unreached")
    } catch (e: ArithmeticException) {
        println("Caught ArithmeticException")
    }
}

控制臺輸出

Throwing exception from launch
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
Joined failed job
Throwing exception from async
Caught ArithmeticException

全局異常處理(CoroutineExceptionHandler)

    launch(CoroutineExceptionHandler { _, e ->
      logger.error("Exception when get content list.", e)
      resultHandler.fail()
    }) {
            val deffer1 = async{ awaitResult<List<JsonObject>>{ dbService.getContentList(it) } }
            val deffer2 = async{ awaitResult<List<JsonObject>>{ dbService.getAuthorList(it) } }
            val contents = deffer1.await()
            val authors = deffer2.await()
            val reuslt = contents.map{ content -> 
                content.put("author", authors.filter{ ... }.first())
            }
    }

協程上下文

顧名思義鸠儿,協程上下文表示協程的運行環(huán)境,包括協程調度器厕氨、代表協程本身的Job进每、協程名稱、協程ID等命斧。通過CoroutineContext定義田晚,CoroutineContext被定義為一個帶索引的集合,集合的元素為Element国葬,上面所提到調度器贤徒、Job等都實現了Eelement接口。

由于CoroutineContext被定義為集合汇四,因此在實際使用時可以自由組合加減各種上下文元素接奈。

啟動子協程時,子協程默認會繼承除Job外的所有父協程上下文元素通孽,創(chuàng)建新的Job序宦,并將父Job設置為當前Job的父親。

啟動子協程時背苦,可以指定協程上下文元素互捌,如果父上下文中存在該元素則覆蓋,不存在則添加行剂。


        // 自定義新協程名稱
        launch(CoroutineName("customName")){
            ... ...
        }

調度器

調度器是協程上下文中眾多元素中最重要的一個秕噪,通過CoroutineDispatcher定義,它控制了協程以何種策略分配到哪些線程上運行厚宰。這里介紹幾種常見的調度器

Dispatcher.Default

默認調度器腌巾。它使用JVM的共享線程池,該調度器的最大并發(fā)度是CPU的核心數固阁,默認為2

Dispatcher.Unconfined

非受限調度器壤躲,它不會將操作限制在任何線程上執(zhí)行——在發(fā)起協程的線程上執(zhí)行第一個掛起點之前的操作,在掛起點恢復后由對應的掛起函數決定接下來在哪個線程上執(zhí)行备燃。

Dispathcer.IO

IO調度器碉克,他將阻塞的IO任務分流到一個共享的線程池中,使得不阻塞當前線程并齐。該線程池大小為環(huán)境變量kotlinx.coroutines.io.parallelism的值漏麦,默認是64或核心數的較大者客税。

該調度器和Dispatchers.Default共享線程,因此使用withContext(Dispatchers.IO)創(chuàng)建新的協程不一定會導致線程的切換撕贞。

Dispathcer.Main

該調度器限制所有執(zhí)行都在UI主線程更耻,它是專門用于UI的,并且會隨著平臺的不同而不同

對于JS或Native捏膨,其效果等同于Dispatchers.Default
對于JVM秧均,它是Android的主線程、JavaFx或者Swing EDT的dispatcher之一号涯。
并且為了使用該調度器目胡,還必須增加對應的組件

kotlinx-coroutines-android
kotlinx-coroutines-javafx
kotlinx-coroutines-swing
其它

在其它支持協程的第三方庫中,也存在對應的調度器链快,如Vertx的vertx.dispatcher()誉己,它將協程分配到vertx的EventLoop線程池執(zhí)行。

注意域蜗,由于上下文具有繼承關系巨双,因此啟動子協程時不顯式指定調度器時,子協程和父協程是使用相同調度器的霉祸。

Job

Job也是上下文元素筑累,它代表協程本身。Job能夠被組織成父子層次結構脉执,并具有如下重要特性疼阔。

父Job退出,所有子job會馬上退出
子job拋出除CancellationException(意味著正常取消)意外的異常會導致父Job馬上退出
類似Thread半夷,一個Job可能存在多種狀態(tài)

State isActive isCompleted isCancelled
New (optional initial state) false false false
Active (default initial state) true false false
Completing (transient state) true false false
Cancelling (transient state) false false true
Cancelled (final state) false true true
Completed (final state) false true false

作用域

協程作用域——CoroutineScope婆廊,用于管理協程,管理的內容有

  • 啟動協程的方式 - 它定義了launch巫橄、async淘邻、withContext等協程啟動方法(以extention的方式),并在這些方法內定義了啟動子協程時上下文的繼承方式湘换。
  • 管理協程生命周期 - 它定義了cancel()方法宾舅,用于取消當前作用域,同時取消作用域內所有協程彩倚。
  fun test(){
        viewModelScope.launch(Dispatchers.Main) {
            print("1:" + Thread.currentThread().name)
            withContext(Dispatchers.IO){
                delay(1000)
                print("2:" + Thread.currentThread().name)
            }
            print("3:" + Thread.currentThread().name)
        }
    }
    //1,2帆离,3處分別輸出main,DefaultDispatcher-worker-1,main

區(qū)分作用域和上下文

從類定義看蔬蕊,CoroutineScope和CoroutineContext非常類似哥谷,最終目的都是協程上下文岸夯,但正如Kotlin協程負責人Roman Elizarov在Coroutine Context and Scope中所說麻献,二者的區(qū)別只在于使用目的的不同——作用域用于管理協程;而上下文只是一個記錄協程運行環(huán)境的集合煮盼。

Flow

我的理解跟rxjava 差不多,感興趣可以看官方文檔。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翘紊,隨后出現的幾起案子鹉究,更是在濱河造成了極大的恐慌柳琢,老刑警劉巖灾测,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異展运,居然都是意外死亡纫事,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儒士,“玉大人憋沿,你說我怎么就攤上這事担租〉衷酰” “怎么了奋救?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長反惕。 經常有香客問我尝艘,道長,這世上最難降的妖魔是什么姿染? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任背亥,我火速辦了婚禮,結果婚禮上悬赏,老公的妹妹穿的比我還像新娘隘梨。我一直安慰自己,他們只是感情好舷嗡,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嵌莉,像睡著了一般进萄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锐峭,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天中鼠,我揣著相機與錄音,去河邊找鬼沿癞。 笑死援雇,一個胖子當著我的面吹牛,可吹牛的內容都是我干的椎扬。 我是一名探鬼主播惫搏,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蚕涤!你這毒婦竟也來了筐赔?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤揖铜,失蹤者是張志新(化名)和其女友劉穎茴丰,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡贿肩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年峦椰,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汰规。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡汤功,死狀恐怖,靈堂內的尸體忽然破棺而出控轿,到底是詐尸還是另有隱情冤竹,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布茬射,位于F島的核電站鹦蠕,受9級特大地震影響,放射性物質發(fā)生泄漏在抛。R本人自食惡果不足惜钟病,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刚梭。 院中可真熱鬧肠阱,春花似錦、人聲如沸朴读。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衅金。三九已至噪伊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氮唯,已是汗流浹背鉴吹。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惩琉,地道東北人豆励。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像瞒渠,于是被迫代替她去往敵國和親良蒸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容