栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Kotlin协程笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Kotlin协程笔记

一、官方文档
    kotlin协程官方中文教程,链接
    本质上,协程是轻量级的线程。 
    它们在某些 CoroutineScope 上下文中与 launch 协程构建器 一起启动。 
    我们在 GlobalScope 中启动了一个新的协程,这意味着新协程的生命周期只受整个应用程序的生命周期限制。
    谷歌协程官方中文教程,链接
    协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。
    
    协程是我们在 Android 上进行异步编程的推荐解决方案。值得关注的特点包括:
    轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
    内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
    内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
    Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。
    安卓协程最佳实践,链接
    1.注入调度程序:在创建新协程或调用 withContext 时,请勿对 Dispatchers 进行硬编码。
    2.挂起函数能够安全地从主线程调用:把会阻塞主进程的代码放到对应的分发器中(如Dispatchers.IO)。
    3.在ViewModel层创建协程:首选在viewmodel中创建协程,视图不应直接触发任何协程来执行业务逻辑,便于测试,且旋转屏幕时会保留协程,延长协程的生命周期,若用lifecycleScope需额外处理。
    4.不要公开可变类型:公开StateFlow 而不是 MutableStateFlow,防止外部修改flow的值
    5.在业务层和数据层中创建协程时:使用coroutineScope或supervisorScope
    6.尽量不使用GlobalScope,如要使用,参考下面链接

二、协程组成:
    作用域CoroutineScope,所有协程都必须在一个作用域内运行。一个 CoroutineScope 管理一个或多个相关的协程。作用域是一个接口,里面是一个CoroutineContext接口,包含了协程的上下文环境,并定义了一些操作符,context之间可以相加。Kotlin自带全局作用域GlobalScope,适用于:数据库存储或者全局网络请求,不同于WorkManager。官网建议不使用,建议自建Application作用域(见),构造器,在指定的作用域CoroutineScope中,通过launch、async等方法创建,创建时可以指定上下文context,可以指定start-协程启动方式,返回一个 Job、Deferred(也就是当前创建出来的协程引用句柄,里面存放了协程的状态和操作协程的方法,其中join方法是等待所有子协程运行结束),其中Job无返回值,Deferred有返回值(使用await获取返回值)     
    其他构造器  produce -> ReceiveChannel   actor -> SendChannel
      上下文 CoroutineContext: 上述方法创建时传入(可选,如不传则为EmptyCoroutineContext, 继承外部上下文),可用来记录当前协程的状态和配置信息,可配置运行在哪个线程池,异常处理等,重载了"+"操作符,可以合并上下文的任务。其衍生子类有:Job用来控制协程的生命周期,CoroutineDispatcher调度线程,CoroutineName用来调试,CoroutineExceptionHandler 异常处理 。通过以上四种来标记和控制协程的执行方式。
    调度器 CoroutineDispatcher,是上下文CoroutineContext的子类,用withContext改变上下文。有以下几种方法创建:
    1.Dispatchers.Default 默认的调度器,限制CPU核数的线程数  
    2.Dispatchers.IO  默认有64个io线程
    3.Dispatchers.Unconfined 不受限,运行在主线程中,通常不需要用到
    4.newSingleThreadContext + newFixedThreadPoolContext 专用线程池,很昂贵
    5.通过Executor的扩展方法asCoroutineDispatcher 创建

    挂起函数suspend:修饰词,标明这个方法必须在挂起函数内部或者在协程作用域内才能调用,无其他作用。如要在挂起函数中创建子协程,可通过coroutineScope继承外部作用域。suspendCoroutine 会将当前的协程挂起,接受一个 Lambda 表达式,通过 resume 或 resumeWithException 可以让协程恢复运行,可以拿到返回值。

三、用法:
上面提到所有的协程都要运行在一个特定的作用域里面,作用域可以嵌套,但是外层作用域结束,所有子作用域都会结束。
如果子作用域使用withContext启动全局作用域GlobalScope则这个子作用域会继续运行,直到结束。所以官方不建议使用GlobalScope,耗资源易内存泄漏
1.创建作用域 -> 2.在作用域中创建协程 -> 3.启动协程,传入任务 -> 4.执行耗时任务(切换线程,调用挂起函数,返回值) -> (启动子协程 -> 执行子协程 -> 子协程结束) 
 -> 5.等待所有协程结束join()
其中2,3,5可以通过系统自带的runBlocking完成,通过launch、async、produce创建子协程并运行,具体用法见下
    创建协程--使用已有作用域,目前安卓有: viewModelScope,lifecycleScope
    fun main() = runBlocking {
        val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用
            delay(1000L)
            println("World!")
        }
        println("Hello,")
        job.join() // 等待直到子协程执行结束
    }
    
    //结构化并发,不用GlobalScope就不需要手动调用join,运行结束自动销毁
    fun main() = runBlocking { // this: CoroutineScope
        launch { // 在 runBlocking 作用域中启动一个新协程
            delay(1000L)
            println("World!")
        }
        println("Hello,")
    }
    
    //android lifecycle
    lifecycleScope.launch {
                //Do Something
            }
    //android viewmodel
    viewModelScope.launch {//Do Something
            }
    创建协程--自定义作用域,通过CoroutineScope或MainScope
    public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
        ContextScope(if (context[Job] != null) context else context + Job())
    
    public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
    
    val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
    val scope = MainScope()
    
    scope.launch { ... }
    协程的取消和超时
    val job = launch {...}
    job.cancel() // 取消该作业
    job.join() // 等待作业执行结束
    job.cancelAndJoin() // 取消该作业并且等待它结束
    withTimeout(1000L) {...} //扔出TimeoutCancellationException
    withTimeoutOrNull(1000L) {...} //不会异常
    协程合并
    使用async来并发两个任务,缩减时间,如果不用async,需要第一个完成才执行第二个任务,时间是双倍,如果用async来异步执行可以缩短到一个时间。
    import kotlinx.coroutines.*
    import kotlin.system.*
    
    fun main() = runBlocking {
        val time = measureTimeMillis {
            val one = async { doSomethingUsefulOne() }
            val two = async { doSomethingUsefulTwo() }
            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
    }
    
    The answer is 42
    Completed in 1024 ms

    如果在协程作用域外部,想要组合多个耗时任务同时运行,可使用coroutineScope,而不需要每个方法传递作用域,再在作用域内启用async来启动异步任务,它会自动继承调用这个suspend方法的作用域

    import kotlinx.coroutines.*
    import kotlin.system.*
    
    fun main() = runBlocking {
        val time = measureTimeMillis {
            println("The answer is ${concurrentSum()}")
        }
        println("Completed in $time ms")    
    }
    
    suspend fun concurrentSum(): Int = coroutineScope {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        one.await() + two.await()
    }
    
    suspend fun doSomethingUsefulOne(): Int {
        delay(1000L) // 假设我们在这里做了些有用的事
        return 13
    }
    
    suspend fun doSomethingUsefulTwo(): Int {
        delay(1000L) // 假设我们在这里也做了些有用的事
        return 29
    }
    
    
    The answer is 42
    Completed in 1024 ms

    注意:取消始终通过协程的层次结构来进行传递,如果第一个耗时任务正在执行,同时启动了第二个耗时任务,然后异常退出,传递到父作用域,父作用域会停止所有的子作用域和其内部的协程,也就是说第一个正在执行的耗时任务也会被取消。

    异步流flow

    通道Channel

    共享值ActorsCoroutineExceptionHandler:异常处理在android中使用协程
四、总结

个人理解:协程就是一个在特定作用域中拥有调度器的代码块,可以按照指定的方式在对应的线程上运行、切换、取消等操作。中间还有很多理解不太清楚的地方,希望读者能自行甄别,如有错误,欢迎在留言区留言,看到即改。买的《kotlin实战》还在路上,到了好好看看。其他优秀教程

1. 外部作用域需要等内嵌作用域结束后才会继续执行外部作用域其余的代码

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch {
        println("线程id:${Thread.currentThread().id} 运行协程1")
        delay(200L)
        println("协程1执行完毕")
    }
    launch {
        println("线程id:${Thread.currentThread().id} 运行协程2")
        delay(1500)
        println("协程2执行完毕")
    }
    coroutineScope { // 创建一个协程作用域
        launch {
            println("线程id:${Thread.currentThread().id} 运行子作用域的协程")
            delay(2000L)
            println("子作用域的协程执行完毕")
        }

        println("子作用域结束") // 这一行会在内嵌 launch 之前输出
    }

    println("外部作用域结束") // 这一行在内嵌 launch 执行完毕后才输出
}


子作用域结束
线程id:1 运行协程1
线程id:1 运行协程2
线程id:1 运行子作用域的协程
协程1执行完毕
协程2执行完毕
子作用域的协程执行完毕
外部作用域结束

2. 异常通过协程的层次结构来进行传递,内部协程异常,会导致父协程也异常,一层层上传。

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        failedConcurrentSum()
    } catch(e: ArithmeticException) {
        println("Computation failed with ArithmeticException")
    }
}

suspend fun failedConcurrentSum(): Int = coroutineScope {
    val one = async { 
        try {
            delay(Long.MAX_VALUE) // 模拟一个长时间的运算
            42
        } finally {
            println("First child was cancelled")
        }
    }
    val two = async { 
        println("Second child throws an exception")
        throw ArithmeticException()
    }
    one.await() + two.await()
}

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/736856.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号