Kotlin-协程

412 阅读10分钟

前言

Google官网推荐将Kotlin协程作为Android异步编程解决方案:

  1. 轻量:在单线程可运行多个协程,挂起(其实就是切线程)
  2. Jetpack集成:Jetpack库支持全面协程扩展。提供协程作用域。
  3. 避免前嵌套地狱
  4. 挂起(suspend):切线程(把切线程简单化了)。suspend修饰的是挂起函数,它只是一个标识(一般认为这个方法耗时或延迟执行),真正的切线程是函数中的特定代码。
  5. 主线程挂起(切线程)像写同步代码一样简单
fun main() {
    log("main")
    // GlobalScope全局作用域,main退出也就结束了
    // 守护协程(类似于守护线程):当main线程退出时,守护协程就得不到执行。
    // 则需要在下面添加Thread.sleep(100)。防止JVM过早退出,launch得不到执行
    GlobalScope.launch(context = Dispatchers.IO) {
        log("launch start")
        delay(100)
        log("launch end")
    }
    log("main delay")
    Thread.sleep(200)
    log("main delay end")
}

private fun log(msg:Any?)= println("[${Thread.currentThread().name}] $msg")
输出:
[Test worker] main
[Test worker] main delay
[DefaultDispatcher-worker-1 @coroutine#1] launch start
[DefaultDispatcher-worker-1 @coroutine#1] launch end
[Test worker] main delay end

两个线程,可以看出是异步的。GlobalScope是全局作用域。

  1. suspend function:挂起函数,delay是协程库提供的挂起函数。
  2. CoroutineScope:协程作用域,GlobalScope是CoroutineScope的实现类,所有协程通过CoroutineScope来启动。
  3. CouroutineContext:协程上下文。Dispatchers.IO是CoroutineContext实现,指定协程运行的载体(运行在哪类线程上)
  4. CoroutineBuilder:协程构建器,通过launch async等协程构建器声明并启动。launch async为CoroutineScope扩展方法。

suspend修饰的函数是挂起函数。只能由协程或其他挂起函数调用。一般这个修饰的都是耗时的或者延迟执行的。真正的挂起是函数具体执行的(切线程)。

  1. 处理耗时任务(子线程)
  2. 保证线程安全安全的从主线程调用任何suspend函数

CoroutineScope

协程作用域,对协程进行跟踪。launch或async创建协程,scope.cancel()取消协程。例如:ViewModel有ViewModelScope, lifecycle有LifecycleScope。分三种:

  1. GlobalScope:全局唯一作用域,不会阻塞当前线程(不会阻止JVM结束运行)。应用停止它就停止。
  2. runBlocking:顶层函数,会阻塞当前线程。
  3. 自定义CoroutineScope:控制协程生命周期,可以在Activity Fragment ViewModel等具有生命周期对象中,避免内存泄漏。
fun main() {
    log("main")
    // 全局作用域,守护协程。一直运行到程序停止,但不会阻止jvm结束运行(不会阻塞当前线程)
    GlobalScope.launch(Dispatchers.IO) {
        log("launch")
        // 延迟100ms执行,挂起函数。
        delay(100)
        log("launch end")
    }
    // 主动休眠200ms,这个是阻塞的。防止jvm结束运行,保证launch得到执行。
    Thread.sleep(200)
    log("main end")
}
输出:
[Test worker] main
[DefaultDispatcher-worker-1 @coroutine#1] launch
[DefaultDispatcher-worker-1 @coroutine#1] launch end
[Test worker] main end

测试代码运行在线程名为:Test worker的线程中。GlobalScope.launch运行在线程名为:DefaultDispatcher-worker-1 @coroutine#1的线程中。这里Thread.sleep是阻塞的,协程的delay是挂起(非阻塞的)类似于Handler发送一个延迟任务。不同的是这个延迟任务是之后要执行的协程域。

fun main() {
    log("main")
    GlobalScope.launch {
        log("launch1")
        launch {
            log("launch2")
            delay(400)
            log("launch2 end")
        }
        launch {
            log("launch3")
            delay(300)
            log("launch3 end")
        }
        log("launch1 end")
    }
    log("main sleep")
    // 保证守护协程执行完毕
    Thread.sleep(500)
    log("main sleep end")
}
输出:
[Test worker] main
[Test worker] main sleep
[DefaultDispatcher-worker-1 @coroutine#1] launch1
[DefaultDispatcher-worker-2 @coroutine#2] launch2
[DefaultDispatcher-worker-1 @coroutine#1] launch1 end
[DefaultDispatcher-worker-3 @coroutine#3] launch3
[DefaultDispatcher-worker-2 @coroutine#3] launch3 end
[DefaultDispatcher-worker-2 @coroutine#2] launch2 end
[Test worker] main sleep end

可以看到launch是挂起函数异步的,线程DefaultDispatcher-worker-3 @coroutine#3 启动的launch3,但是下面是DefaultDispatcher-worker-2 @coroutine#3 结束launch3的执行。明显一个协程(代码块)里有两个线程执行(DefaultDispatcher-worker-3被释放了,启用当前缓存的DefaultDispatcher-worker-2)。资源得到及时释放。

GlobalScope可能导致内存泄漏,开发中慎用(相当于子线程的死循环Android 主线程main函数)。

fun main() {
    log("main")
    runBlocking {
        log("runBlocking")
        launch {
            log("launch1")
            repeat(3) {
                delay(100)
            }
            log("launch1 end")
        }
        launch {
            log("launch2")
            repeat(3) {
                delay(100)
            }
            log("launch2 end")
        }
        GlobalScope.launch {
            log("launch3")
            repeat(3) {
                delay(100)
            }
            log("launch3 end")
        }
        log("runBlocking end")
    }
    log("main end")
}
输出:
[Test worker] main
[Test worker @coroutine#1] runBlocking
[Test worker @coroutine#1] runBlocking end
[DefaultDispatcher-worker-2 @coroutine#4] launch3
[Test worker @coroutine#2] launch1
[Test worker @coroutine#3] launch2
[Test worker @coroutine#2] launch1 end
[DefaultDispatcher-worker-2 @coroutine#4] launch3 end
[Test worker @coroutine#3] launch2 end
[Test worker] main end

runBlocking是阻塞的,它的内部又调用了launch,所以runBlocking内部又是异步的。当runBloking和它内部的异步代码执行完毕,才结束当前main线程。全局作用域的协程DefaultDispatcher-worker-2执行优先级高。

fun main() {
    log("main")
    GlobalScope.launch(Dispatchers.IO) {
        delay(600)
        log("launch")
    }
    runBlocking {
        delay(500)
        log("runBlocking")
    }
    // 保证GlobalScope.launch守护协程得到执行完
    Thread.sleep(650)
    log("main end")
}
输出:
[Test worker] main
[Test worker @coroutine#2] runBlocking
[DefaultDispatcher-worker-1 @coroutine#1] launch
[Test worker] main end

总共有三个线程,runBlocking是阻塞当前线程的协程。launch是异步的协程。

fun main() = runBlocking {
    launch {
        delay(100)
        log("launch")
    }
    // 创建独立的协程作用域,所有启动的协程完成后才结束自身
    coroutineScope {
        launch {
            delay(500)
            log("coroutineScope#launch2")
        }
        delay(50)
        log("coroutineScope")
    }
    log("runBlocking is over")
}
输出:
[Test worker @coroutine#1] coroutineScope
[Test worker @coroutine#2] launch
[Test worker @coroutine#3] coroutineScope#launch2
[Test worker @coroutine#1] runBlocking is over

coroutineScope和runBlocking在同一个协程中所以会阻塞runBlocking。coroutineScope内部启动launch一个协程。corouttineScope内所有协程完成后才结束自身(runBlocking is over等待coroutineScope#launch2输出后才会打印)。

作用域可以分为三种:

  1. GlobalScope:全局协程作用域,启动的协程相当于守护线程,不会阻止JVM结束运行
  2. runBlocking:顶层函数,会阻塞当前线程直到内部所有协程作用域的协程执行结束
  3. 自定义CoroutineScope:控制协程的生命周期,可以在Activity Fragment ViewModel等具有生命周期对象中,避免内存泄漏。

ViewModel有ViewModelScope lifecycle有LifecycleScope

作用域解决Android生命周期内存泄漏问题。

  1. 常用的ViewModel作用域是ViewModelScope,默认调度器Dispatchers.Main。Activity或Fragment可以使用lifecycleScope,任务自动与宿主隔离,lifecycleScope默认调度器是Dispatchers.Main
  2. GlobalScope作用域是这个App的生命周期,所以没有作用域。默认调度器是Dispatchers.default。
fun main() {
    log("main")
    // 全局作用域,守护协程。一直运行到程序停止,但不会阻止jvm结束运行(不会阻塞当前线程)
    GlobalScope.launch(Dispatchers.IO) {
        log("launch")
        // 延迟100ms执行,挂起函数。
        delay(100)
        log("launch end")
    }
    runBlocking {
        // runBlocking会阻塞当前线程200ms,所以上面的守护协程在200ms内可以得到执行
        delay(200)
        log("runBlocking")
    }
    log("main end")
}
输出:
[Test worker] main
[DefaultDispatcher-worker-1 @coroutine#1] launch
[DefaultDispatcher-worker-1 @coroutine#1] launch end
[Test worker @coroutine#2] runBlocking
[Test worker] main end

当前测试环境的线程名:Test worker

runBlocking运行的线程名:Test worker @coroutine#2。是阻塞的

supervisorScope

不改变当前协程,是一个挂起函数,在这个函数中开启的协程,抛出的异常不会连锁取消同级协程和父协程。

fun main() = runBlocking {
    launch {
        delay(100)
        // @coroutine#2
        log("launch")
    }
    // supervisorScope挂起函数(不会改变当前协程),开启的协程(launch)作用域抛出的异常不会连锁取消同级协程和父协程。
    supervisorScope {
        log("supervisorScope")
        launch {
        // @coroutine#3
            log("supervisorScope launch delay failed")
            delay(500)
            throw Exception("supervisorScope failed")
        }
        launch {
            delay(600)
            // @coroutine#4
            log("supervisorScope launch")
        }
    }
    // @coroutine#1
    log("runBlocking")
}
输出:
[Test worker @coroutine#1] supervisorScope
[Test worker @coroutine#3] supervisorScope launch delay failed
[Test worker @coroutine#2] launch
supervisorScope failed
[Test worker @coroutine#4] supervisorScope launch
[Test worker @coroutine#1] runBlocking

MainScope

class MainActivity : ComponentActivity() {
    // 主协程作用域
    private val mainScope = MainScope()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mainScope.launch {
            repeat(5) {
                // delay是挂起函数,非阻塞。类似于发送个Handler延迟10秒执行的task
                delay(1000L * 10)
                // 如果用Thread.sleep(1000L*10)会阻塞线程,发生ANR(遥控器左右键或触摸屏幕)
                log("launch it:$it")
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        mainScope.cancel()
        log("cancel")
    }
}
输出:
[main] launch before
[main] launch after
[main] launch it:0
[main] launch it:1
[main] cancel

上面是通过关闭当前activity,如果还未打印完则中断。如果关闭当前activity则会打印完5次日志。可以看到线程是主线程,并且不会阻塞。mainScope.launch不会阻塞上下代码。但是mainScope.launch内部代码块是阻塞的(可以在其内部前后打印日志发现最后一行日志是等其内部执行完毕后打印,可能会产生疑问---那为什么不阻塞)。这就是sleep挂起函数的厉害之处(其实就是发送延迟任务,等任务时间到了还原到任务的结束点继续执行)。所以看到都是在主线程执行,但是没有阻塞。

delay是挂起函数,非阻塞,所以不会阻塞主线程,属于线程安全的:

  1. 构造task加入延迟队列中,此时协程挂起(从main线程切走,并不会阻塞是相对于当前线程)。
  2. 有个单独的线程会检测是否需要取出task并执行,没到时间就挂起等待(非阻塞,相对于当前线程)
  3. 时间到了从延迟队列中取出放到正常的队列,并从正常队列里取出执行
  4. task执行的过程就是协程恢复的过程。

使用Handler可以实现延迟10秒(相对delay就复杂了)。但是delay简化了切换的过程。

delay相对于Handler厉害之处在于,挂起点到恢复“挂起点”(不仅挂起了delay下面的就连repeat下面的也在内,你可以在mainScope.launch内最后一行代码打印下日志,内部是“阻塞”的)。mainScope.launch内部是同步代码,可能难以理解怎么会不造成anr。

Activity实现CoroutineScope接口

class MainActivity : ComponentActivity() ,CoroutineScope by CoroutineScope(Dispatchers.Default) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        log("launch before")
        launch{
            repeat(5){
                delay(1000*10)
                log(it)
            }
        }
        log("launch after")
    }

    override fun onDestroy() {
        super.onDestroy()
        cancel()
        log("cancel")
    }
}
输出:
[main threadId:2] launch before
[main threadId:2] launch after
[DefaultDispatcher-worker-1 threadId:257] 0
[DefaultDispatcher-worker-1 threadId:257] 1
[main threadId:2] cancel

Actvity实现CooutineScope接口,by委托CoroutineScope改变协程(Dispatchers.Default)。可以看到直接使用launch来调用协程并且走的是非主线程。在onDestroy调用cancel取消协程。

例如:使用ViewModelScope时,ViewModel会在自身的onCleared方法中自动取消作用域。

launch 函数

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job 
  1. context:协程的上下文
  2. start:指定协程的启动方式;默认值CoroutineStart.DEFAULT,在声明时就立即进入等待调度的状态,立即可执行状态。可以通过设置CoroutineStart.LAZY实现延迟启动,懒加载。
  3. block:传递协程的执行本,执行的任务
fun main() = runBlocking {
    val launchA = launch {
        repeat(3){
            delay(100)
            log("launchA")
        }
    }
    val launchB = launch {
        repeat(3){
            delay(100)
            log("launchB")
        }
    }
    log("runBlocking")
}
输出:
[Test worker @coroutine#1] runBlocking
[Test worker @coroutine#2] launchA
[Test worker @coroutine#3] launchB
[Test worker @coroutine#2] launchA
[Test worker @coroutine#3] launchB
[Test worker @coroutine#2] launchA
[Test worker @coroutine#3] launchB

三个协程异步执行。而各自的协程是同步执行。

Job

协程的句柄。luanch或async创建的协程会返回Job实例,唯一标识并管理生命周期。Job接口类型。

// Job处于活动状态时为true
// 如果Job未被取消或没失败,则处于active状态
public val isActive:Boolean
// 当Job正常结束或者由于异常结束,均返回true
public val isCompleted:Boolean
// 当Job被主动取消或异常结束,返回true
public val isCancelled:Boolean
// 启动Job,启动成功则返回true;已经处于started或completed,则返回false;
public fun start():Boolean
// 取消Job,传入Exception来标明取消原因
public fun cancel(cause:CancellationException?=null)
// Job结束运行回调此方法,用于接收可能存在的运行异常
public fun invokeOnCompletion(handler:CompletionHandler):DisposableHandle
StateisActiveisCompletedisCancelled
New(optional initial state)falsefalsefalse
Active(default initial state)truefalsefalse
Completing(transient state)truefalsefalse
Cancelling(transient state)falsefalsetrue
Cancelled(final state)falsetruetrue
Completed(final state)falsetruefalse
fun main() {
    val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
        for (i in 0..100) {
            // 每次循环延迟100ms
            delay(100)
        }
    }
    job.invokeOnCompletion {
        log("invokeOnCompletion:$it")
    }
    log("isActive:${job.isActive} isCancelled:${job.isCancelled} isCompleted:${job.isCompleted}")
    job.start()
    log("isActive:${job.isActive} isCancelled:${job.isCancelled} isCompleted:${job.isCompleted}")
    // 休眠400ms
    Thread.sleep(400)
    // 取消协程
    job.cancel(CancellationException("test"))
    // 休眠400ms防止JVM过快导致invokeOnCompletion来不及回调
    Thread.sleep(400)
    log("isActive:${job.isActive} isCancelled:${job.isCancelled} isCompleted:${job.isCompleted}")
}
输出:
[Test worker] isActive:false isCancelled:false isCompleted:false
[Test worker] isActive:true isCancelled:false isCompleted:false
[DefaultDispatcher-worker-1 @coroutine#1] invokeOnCompletion:java.util.concurrent.CancellationException: test
[Test worker] isActive:false isCancelled:true isCompleted:true
val job = Job()
val scope = CoroutineScope(job + Dispatchers.IO)
fun main() = runBlocking {
    log("job:$job")
    val job = scope.launch {
        try {
            delay(1000)
        } catch (e: CancellationException) {
            log("job is canceled")
            throw e
        }
        log("end")
    }
    delay(1000)
    log("scope job is ${scope.coroutineContext[Job]}")
    scope.coroutineContext[Job]?.cancel()
    log("runBlocking end")
}
输出:
[Test worker @coroutine#1] job:JobImpl{Active}@2931522b
[Test worker @coroutine#1] scope job is JobImpl{Active}@2931522b
[DefaultDispatcher-worker-1 @coroutine#2] end
[Test worker @coroutine#1] runBlocking end

可以通过coroutineContext[Job]从上下文获取到。job和scope.coroutineContext[Job]是同一个。

async

是CoroutineScope扩展函数,async可返回协程执行结果,launch不行。

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> 
fun main() {
    // 统计耗时
    val time = measureTimeMillis {
        runBlocking {
            val asyncA = async {
                delay(3000)
                1
            }
            val asyncB = async {
                delay(4000)
                2
            }
            // 拿到两个协程的执行结果
            log(asyncA.await() + asyncB.await())
        }
    }
    log(time)
}
输出:
[Test worker @coroutine#1] 3
[Test worker] 4085

await拿到async执行结果,总耗时接近最大的那个。launch和async仅在CouroutineScope中使用,任何创建的协程都会被scope追踪。kotlin禁止创建不能够被追踪的协程,避免协程泄漏。

fun main() {
    // 统计耗时
    val time = measureTimeMillis {
        runBlocking {
            val asyncA = async(start = CoroutineStart.LAZY) {
                delay(3000)
                log("asyncA")
                1
            }
            val asyncB = async(start = CoroutineStart.LAZY) {
                delay(4000)
                log("asyncB")
                2
            }
            // 拿到两个协程的执行结果
            log(asyncA.await() + asyncB.await())
        }
    }
    log(time)
}
输出:
[Test worker @coroutine#2] asyncA
[Test worker @coroutine#3] asyncB
[Test worker @coroutine#1] 3
[Test worker] 7095

CoroutineStart.LAZY不会主动启动协程,直到调用async.await或async.start后才会启动(懒加载模式)。导致asyncA.await()+asyncB.await两个协程顺序执行。默认值CoroutineStart.DEFAULT协程在声明时就被启动了(需要等待调度执行,可看作立即执行),调用第一个async.await时两个协程其实都处于运行状态,总耗时4s左右。

fun main() {
    runBlocking {
        val job1  =async {
            fun1()
            log("fun1")
        }
        job1.join()
        val job2 = async {
            fun2()
            log("fun2")
        }
        job2.join()
        val job3 = async {
            fun3()
            log("fun3")
        }
        job3.join()
    }
}
输出:
[Test worker @coroutine#2] fun1
[Test worker @coroutine#3] fun2
[Test worker @coroutine#4] fun3

runBlocking内,join方法把三个协程串行执行。launch协程没有返回执行结果。可以使用async.

fun main() {
    runBlocking {
        val job1 = async {
            fun1()
            log("fun1")
            "result1"
        }
        log(job1.await())
        val job2 = async {
            fun2()
            log("fun2")
            "result2"
        }
        log(job2.await())
        val job3 = async {
            fun3()
            log("fun3")
            "result3"
        }
        log(job3.await())
    }
}
输出:
[Test worker @coroutine#2] fun1
[Test worker @coroutine#1] result1
[Test worker @coroutine#3] fun2
[Test worker @coroutine#1] result2
[Test worker @coroutine#4] fun3
[Test worker @coroutine#1] result3

串行执行,并依次获取结果。

suspend fun fun1() = withContext(Dispatchers.IO) {
    delay(2000)
}

suspend fun fun2() = withContext(Dispatchers.IO) {
    delay(1000)
}

suspend fun fun3() = withContext(Dispatchers.IO) {
    delay(3000)
}

@Test
fun main() {
    runBlocking {
        val job1 = async {
            fun1()
            log("fun1")
            "result1"
        }
        val job2 = async {
            fun2()
            log("fun2")
            "result2"
        }
        val job3 = async {
            fun3()
            log("fun3")
            "result3"
        }
        log(awaitAll(job1,job2,job3))
    }
}
输出结果:
[Test worker @coroutine#3] fun2
[Test worker @coroutine#2] fun1
[Test worker @coroutine#4] fun3
[Test worker @coroutine#1] [result1, result2, result3]

三个子协程并发执行使用awaitAll

fun main() = runBlocking {
    launch {
        log("child")
    }
    log("parent")
}
输出:
[Test worker @coroutine#1] parent
[Test worker @coroutine#2] child

runBlocking创建子协程是非阻塞的。创建完子协程后join与await是先于子协程开启前执行的。CoroutineStart.Default,协程体内会稍后执行,创建协程的代码将继续运行。

fun main() = runBlocking {
    val job= launch(start = CoroutineStart.DEFAULT) {
        log("child")
    }
    log("parent:${job.cancel()}")
}
输出:
[Test worker @coroutine#1] parent:kotlin.Unit

由于Default先执行父协程,后执行子协程。所以会先执行取消子协程的操作,子协程还未执行就取消了。

fun main() = runBlocking {
    val job = launch(start = CoroutineStart.DEFAULT) {
        log("child")
    }
    delay(1)
    log("parent:${job.cancel()}")
    log("child isActive:${job.isActive} isCancelled:${job.isCancelled} isComplete:${job.isCompleted}")
}
输出:
[Test worker @coroutine#2] child
[Test worker @coroutine#1] parent:kotlin.Unit
[Test worker @coroutine#1] child isActive:false isCancelled:false isComplete:true

在父协程里延迟1ms执行子协程的cancel。所有子协程有执行的机会,子协程得到了执行。 delay是挂起函数,挂起1ms的时候job已经得到了执行,所以1ms恢复到挂起点继续执行。

fun main() {
    runBlocking {
        val job = launch(start = CoroutineStart.ATOMIC) {
            log("child")
            delay(100)
            log("child end")
        }
        log("parent:${job.cancel()}")
    }
}
输出:
[Test worker @coroutine#1] parent:kotlin.Unit
[Test worker @coroutine#2] child

虽然在runBlocking已经取消了,但是由于ATOMIC限制给了执行机会,然后由于delay延迟则cancel得到了执行。可以理解为原子性。

ATOMIC协程启动前是无法取消它的。

  1. 协程创建后启动前无法被取消
  2. 如果在协程运行后调用协程的取消方法,协程isActive=false isCancelled=true,并且会子在下一次挂起点停止执行。

LAZY

只有手动启动后才会执行。

  1. start类似于Default协程在调用start方法后稍后执行,并且不影响该方法的后续代码的执行
  2. join会启动子协程,并且将当前代码挂起
  3. await能获取返回结果
fun main() {
    runBlocking {
        val job = launch(start = CoroutineStart.LAZY) {
            log("child")
            delay(100)
        }
        log("parent:${job.cancel()}")
        log("child isActive:${job.isActive} isCancelled:${job.isCancelled}")
    }
}

在启动子协程时,将子协程取消,子协程不会被启动。使用start join await等方法启动lazy协程,如果协程启动前被cancle,则协程不会被启动。

UNDISPATCHED

不调度,创建完协程后,不使用调度器来调度,直接同步执行,直到遇到下一个挂起点才会按照设置的调度器来执行。

fun main() {
    runBlocking {
        val job = launch(start = CoroutineStart.UNDISPATCHED) {
            log("child")
            delay(100)
            log("child end")
        }
        log("child isActive:${job.isActive} isCancelled:${job.isCancelled}")
        log("parent:${job.cancel()}")
        log("child isActive:${job.isActive} isCancelled:${job.isCancelled}")
    }
}
输出:
[Test worker @coroutine#2] child
[Test worker @coroutine#1] child isActive:true isCancelled:false
[Test worker @coroutine#1] parent:kotlin.Unit
[Test worker @coroutine#1] child isActive:false isCancelled:true

子协程体的diamond块执行时机优先于父协程体的后续代码块。

  1. UNDISPATCHED 启动的协程会优先于父协程下面的代码块执行(顺序执行),直到遇到第一个挂起点(delay)才会按照挂起点的线程逻辑执行后续代码,在创建后,子协程无法被父协程取消(挂起点后的可以取消掉)
  2. 如果在协程运行后调用协程的取消方法,isActive=false iscancelled=true,会在下一次挂起点停止执行(挂起点后的可以取消掉)

CoroutineDispatcher

协程调度器。所有协程必须在CoroutineDispatcher中运行。协程可以自行暂停,CoroutineDispatcher负责恢复。

  1. Dispatchers.Default:默认调度器,适合占用大量CPU资源的任务(运算密集型)。例如:列表排序和解析JSON
  2. Dispatchers.IO:执行磁盘或者IO任务(任务密集型)。例如:Room组件读写磁盘文件 执行网络请求
  3. Dispatchers.Unconfined:执行的协程线程不做限制,可以在当前调度器所在的线程运行
  4. Dispatchers.Main:Android主线程运行协程,只能用于界面交互和执行快速任务,例如:更新UI调用 LiveData.setValue
fun main() = runBlocking {
    log("runBlocking")
    launch {
        log("launch1")
    }
    launch(Dispatchers.Default) {
        log("launch default")
        launch(Dispatchers.Unconfined) {
            log("launch unconfined")
        }
    }
    launch (Dispatchers.IO){
        log("io")
        launch (Dispatchers.Unconfined){
            log("io unconfined")
        }
    }
    launch (newSingleThreadContext("MyOwnThread")){
        log("newSingleThreadContext")
        launch (Dispatchers.Unconfined){
            log("myOwnThread unconfined")
        }
    }
    launch (Dispatchers.Unconfined){
        log("unconfined")
    }
    GlobalScope.launch {
        log("GlobalScope")
    }
    log("runBlocking end")
}
输出:
[Test worker @coroutine#1] runBlocking
[DefaultDispatcher-worker-1 @coroutine#3] launch default
[DefaultDispatcher-worker-2 @coroutine#4] io
[DefaultDispatcher-worker-1 @coroutine#5] launch unconfined
[DefaultDispatcher-worker-2 @coroutine#6] io unconfined
[Test worker @coroutine#8] unconfined
[Test worker @coroutine#1] runBlocking end
[MyOwnThread @coroutine#7] newSingleThreadContext
[DefaultDispatcher-worker-1 @coroutine#9] GlobalScope
[MyOwnThread @coroutine#10] myOwnThread unconfined
[Test worker @coroutine#2] launch1
  1. launch在不执行Dispatchers情况下使用时(Unconfined不限定具体的线程类型),均在当前线程执行。
  2. IO和Default均依靠后台线程池来执行
  3. GlobalScope默认调度器是Dispatchers.Default,后台线程池执行。
  4. newSingleThreadContext为协程专门创建一个线程(非常昂贵的资源),非必须不要用,用完别忘记释放。

Android ktx

包含在Android Jetpack以及其他Android库中kotlin扩展程序。

  1. lifecycle ktx:每个Lifecycle对象(Activity Fragment Process等)定义了一个LifecycleScope,就有生命周期
  2. ViewModel ktx:提供了ViewModelScope在ViewModel中启动协程,当ViewModel回调了onCreated方法会自动取消该作用域
  3. LiveData ktx: 先完成异步计算任务,根据计算结果向LiveData回调值。