八,Kotlin協(xié)程詳解

概念

Kotlin協(xié)程是一個異步框架,是建立在線程基礎(chǔ)上輕量級的線程.
協(xié)程依賴于線程,但是協(xié)程掛起時不需要阻塞線程,幾乎是無代價的,協(xié)程是由開發(fā)者控制的。所以協(xié)程也像用戶態(tài)的線程,非常輕量級,一個線程中可以創(chuàng)建任意個協(xié)程。
總而言之:
協(xié)程可以簡化異步編程,可以順序地表達(dá)程序,協(xié)程也提供了一種避免阻塞線程并用更廉價、更可控的操作替代線程阻塞的方法 -- 協(xié)程掛起

掛起:保存當(dāng)前運行狀態(tài),釋放資源,此時協(xié)程可去做其它工作,可充分利用資源
阻塞:占用資源未釋放,等待狀態(tài)
使用之前首選需要引入依賴

//kotlin 標(biāo)準(zhǔn)庫
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//依賴協(xié)程核心庫 ,提供Android UI調(diào)度器
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
//依賴當(dāng)前平臺所對應(yīng)的平臺庫 (必須)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"

協(xié)程的優(yōu)勢

  • 輕量級,占用更少的系統(tǒng)資源

  • 更高的執(zhí)行效率

  • 掛起函數(shù)較于實現(xiàn)Runnable或Callable接口更加方便可控

  • kotlin.coroutine 核心庫的支持,讓編寫異步代碼更加簡單

啟動協(xié)程

launch:job 簡單使用示意
GlobalScope 單例對象 可以直接調(diào)用 launch 開啟協(xié)程
創(chuàng)建一個不會阻塞當(dāng)前線程、沒有返回結(jié)果的 Coroutine,但會返回一個 Job 對象,可以用于控制這個 Coroutine 的執(zhí)行和取消,返回值為Job。

// 默認(rèn)在后臺執(zhí)行,可指定線程
GlobalScope.launch {
    LogUtil.i("zz------${Thread.currentThread().name}")
}
//LogUtil: i: zz------DefaultDispatcher-worker-1

runBlocking:T頂層函數(shù)

創(chuàng)建一個會阻塞當(dāng)前線程的Coroutine,常用于單元測試的場景,開發(fā)中一般不會用到
runBlocking {}是創(chuàng)建一個新的協(xié)程同時阻塞當(dāng)前線程,直到協(xié)程結(jié)束。這個不應(yīng)該在協(xié)程中使用,主要是為main函數(shù)和測試設(shè)計的

fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
    launch { // launch new coroutine in background and continue
        delay(1000L)
        println("World!")
    }
    println("Hello,") // main coroutine continues here immediately
    delay(2000L)      // delaying for 2 seconds to keep JVM alive
}

async/await:Deferred
async 返回的 Coroutine 多實現(xiàn)了 Deferred 接口,簡單理解為帶返回值的launch函數(shù)
async {}可以實現(xiàn)與 launch builder 一樣的效果,在后臺創(chuàng)建一個新協(xié)程,唯一的區(qū)別是它有返回值,因為async {}返回的是 Deferred 類型。
獲取async {}的返回值需要通過await()函數(shù),它也是是個掛起函數(shù),調(diào)用時會掛起當(dāng)前協(xié)程直到 async 中代碼執(zhí)行完并返回某個值。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_demo2)
    testAsync()
}

private fun testAsync() = GlobalScope.launch {
    val deferred = async(Dispatchers.IO) {
        delay(3000L)
        "Show Time"
    }
    // 此處獲取耗時任務(wù)的結(jié)果,我們掛起當(dāng)前協(xié)程,并等待結(jié)果
    val result = deferred.await()
    //掛起協(xié)程切換至UI線程 展示結(jié)果
    withContext(Dispatchers.Main) {
        tvText.text = result
    }
}

async和await是兩個函數(shù),這兩個函數(shù)在我們使用過程中一般都是成對出現(xiàn)的。
async用于啟動一個異步的協(xié)程任務(wù),await用于去得到協(xié)程任務(wù)結(jié)束時返回的結(jié)果,結(jié)果是通過一個Deferred對象返回的。

掛起函數(shù)

suspend 修飾的方法稱之為掛起函數(shù),有標(biāo)記和提醒作用
掛起函數(shù)只能在協(xié)程或者另外一個掛起函數(shù)中使用,在協(xié)程中掛起函數(shù)結(jié)束后自動切回協(xié)程主線程
掛起,就是一個稍后會被自動切回來的線程調(diào)度操作。

// 設(shè)置主線程線程環(huán)境
GlobalScope.launch(Dispatchers.Main) {
    // suspend,掛起函數(shù),方法內(nèi)切線程
    ioFun1()
    // 自動回到協(xié)程主線程,上面指定主線程
    uiFun1()
}

/*
suspend 修飾的方法稱之為掛起函數(shù),有標(biāo)記和提醒作用
   掛起函數(shù)只能在協(xié)程或者另外一個掛起函數(shù)中使用,在協(xié)程中掛起函數(shù)結(jié)束后自動切回“協(xié)程主線程”
*/
private suspend fun ioFun1() {
    // 切換線程
    withContext(Dispatchers.IO) {
        LogUtil.i("zz------ioFun1--${Thread.currentThread().name}")
    }
}

private fun uiFun1() {
    LogUtil.i("zz------uiFun1--${Thread.currentThread().name}")
}
I/LogUtil: i: zz------ioFun1--DefaultDispatcher-worker-1
I/LogUtil: i: zz------uiFun1--main

取消協(xié)程

未取消協(xié)程的執(zhí)行

val launch = GlobalScope.launch {
    LogUtil.i("launch------${Thread.currentThread().name}")
    // 耗時任務(wù)
    delay(5000)
    LogUtil.i("delay------${Thread.currentThread().name}")
}
//執(zhí)行結(jié)果:
I/LogUtil: i: launch------DefaultDispatcher-worker-1
I/LogUtil: i: delay------DefaultDispatcher-worker-1

取消協(xié)程后的執(zhí)行

val launch = GlobalScope.launch {
    LogUtil.i("launch------${Thread.currentThread().name}")
    // 耗時任務(wù)
    delay(5000)
    LogUtil.i("delay------${Thread.currentThread().name}")
}
// 取消協(xié)程
launch.cancel()
//執(zhí)行結(jié)果
I/LogUtil: i: launch------DefaultDispatcher-worker-1

從日志的輸入情況來看,協(xié)程取消后其后續(xù)的代碼不在執(zhí)行.

線程的切換

// 開啟協(xié)程
GlobalScope.launch {
    LogUtil.i("launch------${Thread.currentThread().name}")
    // 切換到主線程
    withContext(Dispatchers.Main) {
        LogUtil.i("launch------${Thread.currentThread().name}")
    }
}

I/LogUtil: i: launch------DefaultDispatcher-worker-1
I/LogUtil: i: launch------main

從日志的輸出結(jié)果可以看到線程從子線程切換到了主線程中.
使用GlobalScope.launch函數(shù)開啟協(xié)程。在其中,使用withContext(Dispatchers.Main)將線程切換到主線程

CoroutineDispatcher ,調(diào)度器

協(xié)程調(diào)度器,決定協(xié)程所在的線程或線程池。它可以指定協(xié)程運行于特定的一個線程、一個線程池或者不指定任何線程(這樣協(xié)程就會運行于當(dāng)前線程)。coroutines-core中 CoroutineDispatcher 有兩種標(biāo)準(zhǔn)實現(xiàn) CommonPool 和 Unconfined,Unconfined 就是不指定線程。

launch函數(shù)定義中的DefaultDispatcher實際上就是CommonPool,CommonPool是一個協(xié)程調(diào)度器,其指定的線程為共有的線程池。而且 CoroutineDispatcher 實現(xiàn)了 CoroutineContext 接口,所以才能直接指定context: CoroutineContext = DefaultDispatcher,實際上,協(xié)程上下文中的元素都實現(xiàn)了 CoroutineContext 接口
Kotlin內(nèi)置了四種調(diào)度器:

  • Dispatchers.Default
    默認(rèn)的調(diào)度器,基于JVM上的共享線程池,最大線程數(shù)為CPU核心數(shù)。
  • Dispatchers.IO
    專為IO操作設(shè)計的調(diào)度器,默認(rèn)最大線程數(shù)為64與CPU核心數(shù)的較大值。
  • Dispatchers.Main
    Android中的主線程,UI主線程調(diào)度器。
  • Dispatchers.Unconfined
    無限制調(diào)度器。在第一個掛起點之前,在調(diào)用它的線程中執(zhí)行;之后由該掛起函數(shù)決定。

GlobalScope

GlobalScope是一個特殊的全局CoroutineScope,它不與任何Job綁定。GlobalScope只應(yīng)該使用在生命周期與整個應(yīng)用程序相同、且不被取消的協(xié)程中。

CoroutineContext

CoroutineContext,協(xié)程上下文,是一些元素的集合,主要包括 Job 和 CoroutineDispatcher 元素,可以代表一個協(xié)程的場景。
EmptyCoroutineContext 表示一個空的協(xié)程上下文。

Job & Deferred

Job,任務(wù),封裝了協(xié)程中需要執(zhí)行的代碼邏輯。Job 可以取消并且有簡單生命周期,它有三種狀態(tài)

    //當(dāng) Job 處于活動狀態(tài)時為 true
    //如果 Job 未被取消或沒有失敗,則均處于 active 狀態(tài)
    public val isActive: Boolean

    //當(dāng) Job 正常結(jié)束或者由于異常結(jié)束,均返回 true
    public val isCompleted: Boolean

    //當(dāng) Job 被主動取消或者由于異常結(jié)束,均返回 true
    public val isCancelled: Boolean

    //啟動 Job
    //如果此調(diào)用的確啟動了 Job,則返回 true
    //如果 Job 調(diào)用前就已處于 started 或者是 completed 狀態(tài),則返回 false 
    public fun start(): Boolean

    //用于取消 Job,可同時通過傳入 Exception 來標(biāo)明取消原因
    public fun cancel(cause: CancellationException? = null)

    //阻塞等待直到此 Job 結(jié)束運行
    public suspend fun join()

    //當(dāng) Job 結(jié)束運行時(不管由于什么原因)回調(diào)此方法,可用于接收可能存在的運行異常
    public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
復(fù)制代碼

Coroutine builders

launch函數(shù)屬于協(xié)程構(gòu)建器 Coroutine builders,Kotlin 中還有其他幾種 Builders,負(fù)責(zé)創(chuàng)建協(xié)程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容