kotlin协程学习笔记

集成kotlinx.coroutines

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'

混淆

# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
-keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}

# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembernames class kotlinx.** {
    volatile ;
}

一、挂起协程

fun main() = runBlocking {//阻塞
    val job = GlobalScope.launch {//不阻塞
        println("World!")
    }
    val job2 = launch {//阻塞
        println("World!")
    }
    val job3 = async {//阻塞
       12
    }
    val job4 = async {//阻塞
       12
    }
    oroutineScope { // 创建一个协程作用域,阻塞
        // 在 coroutineScope 作用域中启动一个不阻塞的新协程
        launch { print("") }
        launch { print("") }
    }
    job.join()//阻塞
    val a = job3.await() + job4.await()//阻塞
}

1.协程构建器
runBlocking这个表达式阻塞了主线程,会一直阻塞直到 runBlocking内部的协程和挂起函数执行完毕,慎用

GlobalScope.XXX函数不阻塞当前线程,而是在后台创建一个独立作用域的协程,需要使用job.cancel()结束协程。

coroutineScope 函数阻塞当前线程,但函数内的子协程不会阻塞。

launch函数在父协程中创建一个不阻塞的新协程,但在runBlocking构建器中会阻塞线程,launch (Dispatchers.Default)也不例外

async 类似于 launch,不同的是会有一个返回值,使用 .await() 获取返回值但会阻塞线程;多个async 可以并发执行

2.挂起函数
job.join()阻塞线程一直到子协程执行完毕然后关闭协程,在作用域中启动的协程不需显式调用job.join(),作用域中启动的所有协程都执行完毕后会自动结束

3.提取函数重构
将 launch { …… } 内部的代码块提取到独立的函数中。如果想在函数中使用新协程或挂起函数需要使用 suspend。

fun main() = runBlocking {
    launch { doWorld() }
}

suspend fun doWorld() {
    delay(1000L)
}

二、取消协程

activity 销毁时如果不想手动取消协程,可以使用CoroutineScope,通过 CoroutineScope() 创建或者通过MainScope() 工厂函数。前者创建了一个通用作用域,而后者为使用Dispatchers.Main作为默认调度器的 UI 应用程序创建作用域

//private val defaultScope = CoroutineScope(Dispatchers.Default)
private val mainScope = MainScope()

fun destroy() {
    mainScope.cancel()
    //defaultScope.cancel()
}

override fun onDestroy() {
    super.onDestroy()
    destroy()
}

或者activity实现 CoroutineScope 接口

class Activity : CoroutineScope by CoroutineScope(Dispatchers.Main) {
    fun destroy() {
        cancel() 
    }
    override fun onDestroy() {
        super.onDestroy()
        destroy()
    }
}

但GlobalScope.XXX函数创建的协程不能用上面的方法结束,首先创建一个集合存放协程

private lateinit var listJob:ArrayList
listJob = ArrayList()

将创建的协程的job存放进集合

fun save()  {
    //Activity退出时,GlobalScope.launch不会自动结束
    val job = GlobalScope.launch {
        var i = 0
        //isActive检测协程是否取消,或者调用yield函数
        while (i< 1000 && isActive) {
            //yield()
            println("${I'm sleeping $i ...")
            i++
        }
        //如果父协程结束了,子协程会自动结束
        launch{ println("子协程结束") }
    }
    listJob.add(job)
}

Activity退出时取消协程

override fun onDestroy() {
    super.onDestroy()
    for (job in listJob){
        job.cancel()
    }
}

三、在 finally 中释放资源

协程如果被取消,协程内的挂起函数会抛出异常,需要使用try {……} finally {……} 表达式

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("I'm running finally")
       }
    }
    delay(1300L) // 延迟一段时间
    job.cancelAndJoin() // 取消该任务并且等待它结束
}

job.cancelAndJoin()函数调用后才会触发finally函数中的内容,任何尝试在 finally 块中调用挂起函数的行为都会抛出CancellationException

当你需要在finally函数中挂起一个被取消的协程,你可以将使用withContext 函数以及 NonCancellable 上下文

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            withContext(NonCancellable) {
                delay(1000L)
            }
        }
    }
    delay(1300L) // 延迟一段时间
    job.cancelAndJoin() // 取消该任务并等待它结束
}

四、超时

withTimeoutOrNull 通过返回 null 来进行超时操作,从而替代抛出一个异常

fun main() = runBlocking {
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
        println("Done") // 在它运行得到结果之前取消它
    }
    println("Result is $result")//withTimeoutOrNull返回的result
}

五、通道(实验性的)

Channel函数,通过channe.send挂起协程并发送,channe.receive()接收

fun main() = runBlocking {
    val channe = Channel()
    launch {
        //发送数据
        for (x in 1..5) channe.send(x * x)
    }
    launch {
        for (msg in channe) println(msg)
    }
}

管道

//管道
fun CoroutineScope.produceSquares(): ReceiveChannel = produce {
    for (x in 1..5) send(x * x)
}
//开启一个协程接收通过管道不断发送过来的消息
fun main() = runBlocking {
	//管道
    val squares = produceSquares()
    //简单接收
    squares.consumeEach { println(it) }
}
//多个协程可以共用一个管道
fun main2() = runBlocking {
    val squares = produceSquares()
    repeat(5){
        launch{
            for (msg in channe) println(msg)
        }
    }
}

缓冲

//前四个元素被加入到了缓冲区并且发送者在试图发送第五个元素的时候被挂起
val channe = Channel(4)

计时器通道

//创建计时器通道
val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) 
//超时50毫秒
val nextElement = withTimeoutOrNull(50) { tickerChannel.receive() }
println(nextElement)

六、惰性启动的 async

使用一个可选的参数 start 并传值 CoroutineStart.LAZY,可以对 async 进行惰性操作。 只有当结果需要被 await 或者如果一个 start 函数被调用,协程才会被启动。

fun main() = runBlocking {
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        // 执行一些计算
        one.start() // 启动第一个
        two.start() // 启动第二个
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设我们在这里做了些有用的事
    return 13
}
suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设我们在这里也做了些有用的事
    return 29
}

七、调度器

fun main() = runBlocking {
    //运行在父协程中,这里为runBlocking主协程
    launch { 
        println(Thread.currentThread().name)
    }
    //运行在主线程中
    launch (Dispatchers.Main){
        println(Thread.currentThread().name)
    }
    //不受限的——将工作在主线程中
    launch(Dispatchers.Unconfined) { 
        println(Thread.currentThread().name)
    }
    //不受父协程影响使用共享的后台线程池,在runBlocking中不阻塞
    GlobalScope.launch {
        println(Thread.currentThread().name)
    }
    //使用了共享的后台线程池
    launch(Dispatchers.Default) {
        println(Thread.currentThread().name)
    }
    //为协程的运行启动了一个线程
    launch(newSingleThreadContext("MyOwnThread")) { 
        println(Thread.currentThread().name)
    }
}

Dispatchers.Default 对于消耗CPU资源的计算密集型协程,这是一个合适的选择
Dispatchers.IO 适用于文件I / O和阻塞套接字I / O

八、调试协程

Thread.currentThread().name获取所在线程和协程的名字

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

fun main() = runBlocking {
    val a = async {
        log("I'm computing a piece of the answer")
        6
    }
    val b = async {
        log("I'm computing another piece of the answer")
        7
    }
    log("The answer is ${a.await() * b.await()}")
}

在协程中获取Job

val job = coroutineContext[Job]

CoroutineName 上下文元素可以给线程像给函数命名一样命名。

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main() = runBlocking(CoroutineName("main")) {
    // 运行两个后台值计算
    val v1 = async(CoroutineName("v1coroutine")) {
        delay(500)
        log("Computing v1")
        252
    }
    val v2 = async(CoroutineName("v2coroutine")) {
        delay(1000)
        log("Computing v2")
        6
    }
    log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
}

组合上下文中的元素
有时我们需要在协程上下文中定义多个元素。我们可以使用 + 操作符来实现。 比如说,我们可以显式指定一个调度器来启动协程并且同时显式指定一个命名:

fun main() = runBlocking {
    launch(Dispatchers.Default + CoroutineName("test")) {
        println("I'm working in thread ${Thread.currentThread().name}")
    }
}

线程局部变量

val threadLocal = ThreadLocal() // 声明线程局部变量
fun main() = runBlocking {
    threadLocal.set("main")
    println("Pre-main, current thread: ${Thread.currentThread()}, thread local value:'${threadLocal.get()}'")
    val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
        println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
        yield()
        println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
    }
    job.join()
    println("Post-main, current thread: ${Thread.currentThread()}, thread local value:'${threadLocal.get()}'")
}

加上threadLocal.asContextElement(value = “launch”) , 无论协程执行在什么线程中都是没有问题的

异常处理
CoroutineExceptionHandler会在launch构建器发生异常时调用,而在 async 构建器中注册它没有任何效果。

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }
    val job = GlobalScope.launch(handler) {
        throw AssertionError()
    }
    val deferred = GlobalScope.async(handler) {
        throw ArithmeticException() // 没有打印任何东西,依赖用户去调用 deferred.await()
    }
    joinAll(job, deferred)
}

九、select 表达式(实验性的)

select 表达式可以同时等待多个挂起函数,并选择 第一个可用的。

onReceiveOrNull选择接收

fun CoroutineScope.fizz() = produce {
    while (true) { // 每 300 毫秒发送一个 "Fizz"
        delay(300)
        send("Fizz")
    }
}
fun CoroutineScope.buzz() = produce {
    while (true) { // 每 500 毫秒发送一个 "Buzz!"
        delay(500)
        send("Buzz!")
    }
}
fun main() = runBlocking {
    val fizz = fizz()
    val buzz = buzz()
    select { //  意味着该 select 表达式不返回任何结果
        fizz.onReceiveOrNull { value -> // 这是第一个 select 子句
            if (value == null){//
                println("通道关闭时触发")
            }else{
                println(value)
            }
        }
        buzz.onReceiveOrNull { value -> // 这是第二个 select 子句
            if (value == null){
                println("通道关闭时触发")
            }else{
                println(value)
            }
        }
    }
}

onSend选择发送

fun CoroutineScope.produceNumbers(side: SendChannel) = produce {
    for (num in 1..10) { // 生产从 1 到 10 的 10 个数值
        delay(100) // 延迟 100 毫秒
        select {
            onSend(num) {} // 发送到主通道
            side.onSend(num) {} // 或者发送到 side 通道
        }
    }
}
fun main() = runBlocking {
        val side = Channel() // 分配 side 通道
        launch { // 对于 side 通道来说,这是一个很快的消费者
            //等待side发送
            side.consumeEach { println("Side channel has $it") }
        }
        produceNumbers(side).consumeEach {
            println("Consuming $it")
            delay(250) // 不要着急,让我们正确消化消耗被发送来的数字
        }
        println("Done consuming")
        coroutineContext.cancelChildren()
}

你可能感兴趣的:(安卓基础,kotlin)