Kotlin 協(xié)程

一、關(guān)于協(xié)程

協(xié)程是Kotlin中最重要、最難學(xué)的一塊!

為什么協(xié)程如此重要?

協(xié)程是 Kotlin 對(duì)比 Java 的最大優(yōu)勢(shì)。Java 也在計(jì)劃著實(shí)現(xiàn)自己的協(xié)程:Loom,不過(guò)這個(gè)畢竟還處于相當(dāng)初級(jí)的階段。而 Kotlin 的協(xié)程,可以幫我們極大地簡(jiǎn)化異步、并發(fā)編程、優(yōu)化軟件架構(gòu)。通過(guò)協(xié)程,我們不僅可以提高開(kāi)發(fā)效率,還能提高代碼的可讀性,由此也就可以降低代碼出錯(cuò)的概率。

要記住協(xié)程的幾個(gè) API 很容易,困難的是形成一套完整的協(xié)程知識(shí)體系。其實(shí),學(xué)習(xí)協(xié)程,相當(dāng)于一次編程思維的升級(jí)。協(xié)程思維,它與我們常見(jiàn)的線程思維迥然不同,當(dāng)我們能夠用協(xié)程的思維來(lái)分析問(wèn)題以后,線程當(dāng)中某些棘手的問(wèn)題在協(xié)程面前都會(huì)變成小菜一碟。因此,我們相當(dāng)于多了一種解決問(wèn)題的手段。

其實(shí),如果要用簡(jiǎn)單的語(yǔ)言來(lái)描述協(xié)程的話,我們可以將其稱為:“互相協(xié)作的程序”。

 ///先了解下攜程與普通程序執(zhí)行的區(qū)別
fun  main() = runBlocking {
        val sequence = getSequence()
        printSequence(sequence)
    }

    fun getSequence() = sequence {
        println("Add 1")
        yield(1)
        println("Add 2")
        yield(2)
        println("Add 3")
        yield(3)
        println("Add 4")
        yield(4)
    }

    fun printSequence(sequence: Sequence<Int>) {
        val iterator = sequence.iterator()
        val i = iterator.next()
        println("Get$i")
        val j = iterator.next()
        println("Get$j")
        val k = iterator.next()
        println("Get$k")
        val m = iterator.next()
        println("Get$m")
    }
輸出結(jié)果如下:
I/System.out: Add 1
I/System.out: Get1
I/System.out: Add 2
I/System.out: Get2
I/System.out: Add 3
I/System.out: Get3
I/System.out: Add 4
I/System.out: Get4
協(xié)程執(zhí)行順序圖.png

協(xié)程與普通程序的區(qū)別:

  • 普通程序在被調(diào)用以后,只會(huì)在末尾的地方返回,并且只會(huì)返回一次,而協(xié)程則不受此限制,協(xié)程的代碼可以在任意 yield 的地方掛起(Suspend)讓出執(zhí)行權(quán),然后等到合適的時(shí)機(jī)再恢復(fù)(Resume)。在這個(gè)情況下,yield 是代表了“讓步”的意思。
  • 普通程序需要一次性收集完所有的值,然后統(tǒng)一返回;而協(xié)程則可以每次只返回(yield)一個(gè)值,比如我們前面寫(xiě)的 getSequence() 方法。在這個(gè)情況下,yield 既有“讓步”的意思,也有“產(chǎn)出”的意思。它不僅能讓出執(zhí)行權(quán),還同時(shí)產(chǎn)生一個(gè)值,比如前面的 yield(1),就代表產(chǎn)出的值為 1。

除了 yield 以外,我們也可以借助 Kotlin 協(xié)程當(dāng)中的 Channel 來(lái)實(shí)現(xiàn)類似的代碼模式:


// 看不懂代碼沒(méi)關(guān)系,目前咱們只需要關(guān)心代碼的執(zhí)行結(jié)果
fun main() = runBlocking {
    val channel = getProducer(this)
    testConsumer(channel)
}

fun getProducer(scope: CoroutineScope) = scope.produce {
    println("Send:1")
    send(1)
    println("Send:2")
    send(2)
    println("Send:3")
    send(3)
    println("Send:4")
    send(4)
}

suspend fun testConsumer(channel: ReceiveChannel<Int>) {
    delay(100)
    val i = channel.receive()
    println("Receive$i")
    delay(100)
    val j = channel.receive()
    println("Receive$j")
    delay(100)
    val k = channel.receive()
    println("Receive$k")
    delay(100)
    val m = channel.receive()
    println("Receive$m")
}

執(zhí)行結(jié)果:
I/System.out: Send:1
I/System.out: Receive1
I/System.out: Send:2
I/System.out: Receive2
I/System.out: Send:3
I/System.out: Receive3
I/System.out: Send:4
I/System.out: Receive4

可見(jiàn),以上代碼中的 getProducer() 和 testConsumer() 之間,它們也是交替執(zhí)行的。

如何理解 Kotlin 的協(xié)程?

在 Kotlin 當(dāng)中,協(xié)程是一個(gè)獨(dú)立的框架。跟 Kotlin 的反射庫(kù)類似,協(xié)程并不是直接集成在標(biāo)準(zhǔn)庫(kù)當(dāng)中的。如果我們想要使用 Kotlin 的協(xié)程,就必須手動(dòng)進(jìn)行依賴:


implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'

業(yè)界一直有兩種說(shuō)法:
一種是Kotlin 協(xié)程其實(shí)就是一個(gè)封裝的線程框架。如果我們站在框架的層面來(lái)看的話,這種說(shuō)法也有一定道理:協(xié)程框架將線程池進(jìn)一步封裝,對(duì)開(kāi)發(fā)者暴露出統(tǒng)一的協(xié)程 API。(扔物線)
另一種是:
從包含關(guān)系上看,協(xié)程跟線程的關(guān)系,有點(diǎn)像線程與進(jìn)程的關(guān)系,畢竟協(xié)程不可能脫離線程運(yùn)行。所以,協(xié)程可以理解為運(yùn)行在線程當(dāng)中的、更加輕量的 Task。(朱濤)

協(xié)程的輕量級(jí)

// 直接使用線程
fun main() {
    repeat(1000_000_000) {
        thread {
            Thread.sleep(1000000)
        }
    }

    Thread.sleep(10000L)
}

/*
輸出結(jié)果:
2022-04-24 17:38:15.736 17290-24330/com.example.conroutinesdemo A/conroutinesdem: thread.cc:4192] Unable to create protected region in stack for implicit overflow check. Reason: Out of memory size:  4096
*/
//使用協(xié)程
fun main() =
    runBlocking {
        repeat(10_000_000){
            launch { delay(10000) }
        }
        delay(10000L)
    }

上面這個(gè)例子是官方的demo,為了說(shuō)明協(xié)程性能更好,實(shí)際上是有漏洞的。上面代碼是開(kāi)了十億個(gè)線程。下面的代碼實(shí)際上只有一個(gè)線程池。所以修改為線程池做對(duì)比更好,如下:

fun  main() {
    val executor = Executors.newSingleThreadExecutor();
    val task = java.lang.Runnable {
        Thread.sleep(1000)
        print(".")

    }
    repeat(10_000_000){
        executor.execute(task)
    }
}

還有就是sleep和delay也是有些區(qū)別的,實(shí)際上我們要做對(duì)比應(yīng)該是使用線程池newSingleThreadExecutor來(lái)做對(duì)比,這兩個(gè)實(shí)際上是一樣的。如下:

fun main(){
    val executor = Executors.newSingleThreadScheduledExecutor()
    val task = java.lang.Runnable {
        print(".")
    }
    repeat(10_000_000){
        executor.schedule(task,1,TimeUnit.SECONDS)
    }
}

這樣對(duì)比之后,性能其實(shí)差不多。

如何理解協(xié)程的非阻塞?

首選聊一下線程阻塞是什么?簡(jiǎn)單來(lái)說(shuō),Android主線程會(huì)進(jìn)行輪詢執(zhí)行任務(wù),如果做一些耗時(shí)任務(wù)就會(huì)導(dǎo)致主線程的阻塞,如何不阻塞,那么就是開(kāi)線程執(zhí)行任務(wù)。協(xié)程實(shí)際上也是通過(guò)線程來(lái)不阻塞主線程,只是寫(xiě)法上看不出來(lái)切了線程,看起來(lái)就像是非阻塞的。這也是協(xié)程的特點(diǎn),使用同步代碼來(lái)寫(xiě)異步。其原因就是協(xié)程可以自動(dòng)切走和切回線程,這個(gè)過(guò)程也叫掛起。

協(xié)程的使用

如何啟動(dòng)一個(gè)協(xié)程

一、使用CoroutineScope.launch()
///通過(guò)launch啟動(dòng)協(xié)程
fun  main(){
    //實(shí)際開(kāi)發(fā)不用使用GlobalScope 這里只是講解基礎(chǔ)
    GlobalScope.launch {
        println("協(xié)程開(kāi)始")
        delay(1000)
        println("hello world")
    }

    println("按照同步的思維,這應(yīng)該在協(xié)程之后")
    //這里休眠兩分鐘是因?yàn)橹骶€程銷毀了,協(xié)程也不會(huì)執(zhí)行了
    Thread.sleep(2000)
    println("主程序停止")
}

看下launch的源碼

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
 ....
}

首先是 CoroutineScope.launch(),代表了 launch 其實(shí)是一個(gè)擴(kuò)展函數(shù),而它的“擴(kuò)展接收者類型”是 CoroutineScope。這就意味著,我們的 launch() 會(huì)等價(jià)于 CoroutineScope 的成員方法。而如果我們要調(diào)用 launch() 來(lái)啟動(dòng)協(xié)程,就必須要先拿到 CoroutineScope 的對(duì)象。前面的案例,我們使用的 GlobalScope,其實(shí)就是 Kotlin 官方為我們提供的一個(gè) CoroutineScope 對(duì)象,方便我們開(kāi)發(fā)者直接啟動(dòng)協(xié)程。

接著是第一個(gè)參數(shù):CoroutineContext,它代表了我們協(xié)程的上下文,它的默認(rèn)值是 EmptyCoroutineContext,如果我們不傳這個(gè)參數(shù),默認(rèn)就會(huì)使用 EmptyCoroutineContext。一般來(lái)說(shuō),我們也可以傳入 Kotlin 官方為我們提供的 Dispatchers,來(lái)指定協(xié)程運(yùn)行的線程池。

然后是第二個(gè)參數(shù):CoroutineStart,它代表了協(xié)程的啟動(dòng)模式。如果我們不傳這個(gè)參數(shù),它會(huì)默認(rèn)使用 CoroutineStart.DEFAULT。CoroutineStart 其實(shí)是一個(gè)枚舉類,一共有:DEFAULT、LAZY、ATOMIC、UNDISPATCHED。我們最常使用的就是 DEFAULT、LAZY,它們分別代表:立即執(zhí)行、懶加載執(zhí)行。

第三個(gè)參數(shù)就是 需要一個(gè)無(wú)參數(shù),無(wú)返回值的掛起函數(shù)。

二、runBlocking 啟動(dòng)協(xié)程
fun  main(){
    runBlocking {
        println("協(xié)程開(kāi)始")
        delay(1000)
        println("hello world")
    }

    println("按照同步的思維,這應(yīng)該在協(xié)程之后")
    //    Thread.sleep(2000)
    println("主程序停止")
}

協(xié)程開(kāi)始
hello world
按照同步的思維,這應(yīng)該在協(xié)程之后
主程序停止


fun main() {
    runBlocking {
        println("First:${Thread.currentThread().name}")
        delay(1000L)
        println("Hello First!")
    }
    runBlocking {
        println("Second:${Thread.currentThread().name}")
        delay(1000L)
        println("Hello Second!")
    }
    runBlocking {
        println("Third:${Thread.currentThread().name}")
        delay(1000L)
        println("Hello Third!")
    }   

   // 刪掉了 Thread.sleep    println("Process end!")
}

First:main
Hello First!
Second:main
Hello Second!
Third:main
Hello Third!

從結(jié)果就可以看出來(lái),runBlocking是會(huì)阻塞主線程的,協(xié)程也會(huì)阻塞。這種方式一般用來(lái)做測(cè)試的,代碼中盡量少用。

三、async 啟動(dòng)協(xié)程

async在dart中也有這么一種方式。它能通過(guò)返回的句柄拿到協(xié)程執(zhí)行的結(jié)果。

///需要在as的VM options中配置-Dkotlinx.coroutines.debug才能看到
fun main() = runBlocking{
    println("In runBlocking : ${Thread.currentThread().name}")
    val deferred: Deferred<String> = async {
        println("In async: ${Thread.currentThread().name}")
        delay(1000)
        return@async "任務(wù)完成";
    }
    println("after async : ${Thread.currentThread().name}")

    println("${deferred.await()}")
}


In runBlocking : main @coroutine#1
after async : main @coroutine#1
In async: main @coroutine#2
任務(wù)完成

async 的協(xié)程和runBlocking并不在一個(gè)上面,runBlocking本來(lái)是阻塞的,但是async的協(xié)程在打印語(yǔ)句之后才執(zhí)行。它是在deferred.await()之后才開(kāi)始執(zhí)行的。然后async 可以拿到返回值,這也是async 和launch的區(qū)別。

kotlin 掛起函數(shù)的核心
  • 掛起函數(shù)可以極大的簡(jiǎn)化異步編程,讓我們以同步的方式寫(xiě)異步。
  • 要定義一個(gè)掛起函數(shù),我們只要在普通的函數(shù)上面增加一個(gè)suspend關(guān)鍵字。
  • 掛起函數(shù)擁有掛起和恢復(fù)的能力,對(duì)于同一行代碼來(lái)說(shuō),=左邊和右邊的代碼在不同的線程上,這些都是由- kotlin編譯器在做。
  • 掛起函數(shù)的本質(zhì)是Callback,只是kotlin底層用了一個(gè)高大上的名字叫Contiunation.Kotlin編譯器把Suspend 變成Continuation的過(guò)程叫做CPS。
  • 掛起函數(shù)只能在掛起函數(shù)中調(diào)用,或者是在協(xié)程中調(diào)用。
協(xié)程的生命周期

Job是協(xié)程的句柄,當(dāng)我們用launch或async創(chuàng)建協(xié)程的時(shí)候,會(huì)同時(shí)創(chuàng)建一個(gè)Job并返回。我們通過(guò)job來(lái)理解協(xié)程的生命周期和并發(fā)。

查看launch返回值

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

查看async的返回值

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

public interface Deferred<out T> : Job {

通過(guò)Job我們能干什么?

  • 監(jiān)控協(xié)程的生命狀態(tài)
  • 使用Job來(lái)操控協(xié)程
///協(xié)程的生命周期
fun main() = runBlocking{
    val job = launch {
        delay(1000)
    }
    job.log()
    job.cancel()
    job.log()
}

fun  Job.log(){
    logX("""
       isActive = $isActive
        isCancelled = $isCancelled
        isCompleted = $isCompleted
    """.trimIndent())
}
fun logX(any: Any?){
    println("""
        ============================================================
        $any
        Thread:${Thread.currentThread().name}
        ============================================================
    """.trimIndent())
}

isActive = true 表示協(xié)程處于獲取階段
調(diào)用job.cancel()以后,協(xié)程任務(wù)就取消了,isCancel = true 表示協(xié)程任務(wù)處于取消狀態(tài)。job.log其實(shí)就是對(duì)協(xié)程的監(jiān)控,不過(guò)是被動(dòng)的監(jiān)控。cancel就是對(duì)協(xié)程的操作。
除了cancel操作,還可以job.start,它一般和CoroutineStart.LAZY一起使用。


協(xié)程的生命周期圖.png
協(xié)程的監(jiān)聽(tīng)
///協(xié)程的監(jiān)聽(tīng)
- job.invokeOnCompletion 通過(guò)這個(gè)api我們可以主動(dòng)監(jiān)聽(tīng)協(xié)程完成
- job.join 是一個(gè)“掛起函數(shù)”,它的作用就是:掛起當(dāng)前的程序執(zhí)行流程,等待 job 當(dāng)中的協(xié)程任務(wù)執(zhí)行完畢,然后再恢復(fù)當(dāng)前的程序執(zhí)行流程.它和await的功能是類似的。
fun main() = runBlocking {
    suspend fun download() {
        //模擬下載任務(wù)
        val  time = (Random.nextDouble() * 1000).toLong()
        logX("Delay time = $time")
        delay(time)
    }

    val job = launch(start = CoroutineStart.LAZY) {
        logX("Coroutine start!")
        download()
        logX("Coroutine end!")
    }
    delay(500)
    job.log()
    job.start()
    job.log()
    job.invokeOnCompletion {
        //協(xié)程執(zhí)行完成調(diào)用這里代碼
        job.log()
    }
    //等待協(xié)程完畢執(zhí)行
    job.join()
    logX("Process end")
}

Job的api


public interface Job : CoroutineContext.Element {

    // 省略部分代碼

    // ------------ 狀態(tài)查詢API ------------

    public val isActive: Boolean

    public val isCompleted: Boolean

    public val isCancelled: Boolean

    public fun getCancellationException(): CancellationException

    // ------------ 操控狀態(tài)API ------------

    public fun start(): Boolean

    public fun cancel(cause: CancellationException? = null)

    public fun cancel(): Unit = cancel(null)

    public fun cancel(cause: Throwable? = null): Boolean

    // ------------ 等待狀態(tài)API ------------

    public suspend fun join()

    public val onJoin: SelectClause0

    // ------------ 完成狀態(tài)回調(diào)API ------------

    public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle

    public fun invokeOnCompletion(
        onCancelling: Boolean = false,
        invokeImmediately: Boolean = true,
        handler: CompletionHandler): DisposableHandle

}
Job與結(jié)構(gòu)化并發(fā)

結(jié)構(gòu)化并發(fā)是kotlin協(xié)程的第二大優(yōu)勢(shì)。簡(jiǎn)單來(lái)說(shuō)就是帶有結(jié)構(gòu)和層級(jí)的并發(fā)


fun  main() = runBlocking{
    val parentJob : Job
    var job1 : Job? = null
    var job2 : Job? = null
    var job3 : Job? = null

    parentJob = launch {
        job1 = launch {
            delay(1000)
        }
        job2 = launch {
            delay(3000)
        }
        job3 = launch {
            delay(5000)
        }
    }

    delay(500)
    parentJob.children.forEachIndexed{
        index, job ->
        when (index){
            0 -> println("job1 === job is ${job1 === job}")
            1 -> println("job2 === job is ${job2 === job}")
            2 -> println("job3 === job is ${job3 === job}")
        }
    }

    parentJob.join()
    logX("Process end")

}

job1 === job is true
job2 === job is true
job3 === job is true
============================================================
Process end
Thread:main @coroutine#1
============================================================

上面的結(jié)果說(shuō)明嵌套的協(xié)程,是屬于父子關(guān)系, parentJob.join()會(huì)等待子job都執(zhí)行完,才會(huì)恢復(fù)掛起。


fun  main() = runBlocking {
    val parentJob: Job
    var job1 : Job? = null
    var job2 : Job? = null
    var job3 : Job? = null

    parentJob = launch {
        job1 = launch {
            logX("Job1 start!")
            delay(1000)
            logX("Job1 done") //不會(huì)走
        }

        job2 = launch {
            logX("Job2 start!")
            delay(1000)
            logX("Job2 done")//不會(huì)走
        }

        job3 = launch {
            logX("Job3 start!")
            delay(1000)
            logX("Job3 done")//不會(huì)走
        }
    }
    delay(500)
    parentJob.children.forEachIndexed{
        index, job ->
        when (index){
            0 -> println("job1 === job is ${job1 === job}")
            1 -> println("job2 === job is ${job2 === job}")
            2 -> println("job3 === job is ${job3 === job}")
        }
    }

    parentJob.cancel();
    logX("Process end!")
}

============================================================
Job1 start!
Thread:main @coroutine#3
============================================================
============================================================
Job2 start!
Thread:main @coroutine#4
============================================================
============================================================
Job3 start!
Thread:main @coroutine#5
============================================================
job1 === job is true
job2 === job is true
job3 === job is true
============================================================
Process end!
Thread:main @coroutine#1
============================================================

通過(guò)上面的運(yùn)行結(jié)果,可以看出來(lái),實(shí)際上parentJob.cancel是會(huì)取消掉子job的。

最后,來(lái)一個(gè)簡(jiǎn)單的實(shí)戰(zhàn)優(yōu)化。

fun  main() = runBlocking {
    suspend fun  getResult1() : String {
        delay(1000)//模式耗時(shí)操作
        return "Result1"
    }

    suspend fun  getResult2() : String {
        delay(1000)//模式耗時(shí)操作
        return "Result2"
    }

    suspend fun  getResult3() : String {
        delay(1000)//模式耗時(shí)操作
        return "Result3"
    }

    val results = mutableListOf<String>()

    val time = measureTimeMillis {
        results.add(getResult1())
        results.add(getResult2())
        results.add(getResult3())
    }

    println("$time")
    println(results)

}

通過(guò)結(jié)果可以看出來(lái),這個(gè)是同步在執(zhí)行。我們通過(guò)協(xié)程可以修改成異步的。

fun main() = runBlocking {
    suspend fun getResult1(): String {
        delay(1000)//模式耗時(shí)操作
        return "Result1"
    }

    suspend fun getResult2(): String {
        delay(1000)//模式耗時(shí)操作
        return "Result2"
    }

    suspend fun getResult3(): String {
        delay(1000)//模式耗時(shí)操作
        return "Result3"
    }


    val results: List<String>
    val time = measureTimeMillis {

        var result1 = async { getResult1() }
        var result2 = async { getResult2() }
        var result3 = async { getResult3() }
        results = listOf(result1.await(), result2.await(), result3.await())
    }
    println("$time")
    println("$results")
}

1037
[Result1, Result2, Result3]
CoroutineContext

萬(wàn)物皆context,學(xué)習(xí)下kotin的context,CoroutineContext。它的最主要的用處是切換線程池。

fun main() = runBlocking {
     val user = getUserInfo()
    logX(user)
}

suspend fun getUserInfo():String{
    logX("Before IO Context")
    withContext(Dispatchers.IO){
        logX("In IO Context")
        delay(1000)
    }
    logX("After IO Context")
    return  "BoyCoder"
}


============================================================
Before IO Context
Thread:main @coroutine#1
============================================================
============================================================
In IO Context
Thread:DefaultDispatcher-worker-1 @coroutine#1
============================================================
============================================================
After IO Context
Thread:main @coroutine#1
============================================================
============================================================
BoyCoder
Thread:main @coroutine#1
============================================================

通過(guò)上面的結(jié)果,我們可以發(fā)現(xiàn)withContext可以切換到自定的線程池工作,然后后面的代碼會(huì)自動(dòng)切回之前的線程。

講一下kotlin內(nèi)置的幾個(gè)Dispatcher

  • **Dispatchers.Main ** 它只在UI編程平臺(tái)才有意義,在Android、Swing之類的平臺(tái)上,一般只有Main線程才能繪制UI。
  • Dispatchers.Unconfined 代表無(wú)所謂,當(dāng)前協(xié)程可以運(yùn)行在任意線程之上。
  • Dispatcher.Default 它代表CPU密集型任務(wù)的線程池。一般來(lái)說(shuō),它內(nèi)部線程個(gè)數(shù)跟CPU核心數(shù)量保持一致,最小限制是2.
  • Dispatcher.IO 它代表IO密集型任務(wù)的線程池。它內(nèi)部的線程數(shù)量一般比較多,比如64.
CoroutineScope

在學(xué)習(xí)launch的時(shí)候,我們實(shí)際上是有協(xié)程作用域的,也就是CoroutineScope。

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

它就一個(gè)成員變量CoroutineContext,它是對(duì)CoroutineContext的一層封裝,主要是用來(lái)做批量控制攜程。

fun main()= runBlocking {
    val scope = CoroutineScope(Job())
    scope.launch {

        logX("First start")
        delay(1000)
        logX("First end") //不會(huì)執(zhí)行
    }

    scope.launch {
        logX("Second start")
        delay(1000)
        logX("Second end")//不會(huì)執(zhí)行
    }

    scope.launch {
        logX("Third start")
        delay(1000)
        logX("Third end")//不會(huì)執(zhí)行
    }

    delay(500)
    scope.cancel()
    delay(1000)
}

============================================================
Second start
Thread:DefaultDispatcher-worker-2 @coroutine#3
============================================================
============================================================
First start
Thread:DefaultDispatcher-worker-1 @coroutine#2
============================================================
============================================================
Third start
Thread:DefaultDispatcher-worker-3 @coroutine#4
============================================================

Process finished with exit code 0

從上面的結(jié)果發(fā)現(xiàn): scope.cancel()會(huì)直接把三個(gè)協(xié)程都取消。

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

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

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