一、關(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é)程與普通程序的區(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é)程的監(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é)程都取消。