美文网首页
八,Kotlin协程详解

八,Kotlin协程详解

作者: CarlosLynn | 来源:发表于2021-02-22 21:31 被阅读0次

    概念

    Kotlin协程是一个异步框架,是建立在线程基础上轻量级的线程.
    协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
    总而言之:
    协程可以简化异步编程,可以顺序地表达程序,协程也提供了一种避免阻塞线程并用更廉价、更可控的操作替代线程阻塞的方法 -- 协程挂起

    挂起:保存当前运行状态,释放资源,此时协程可去做其它工作,可充分利用资源
    阻塞:占用资源未释放,等待状态
    使用之前首选需要引入依赖

    //kotlin 标准库
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    //依赖协程核心库 ,提供Android UI调度器
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
    //依赖当前平台所对应的平台库 (必须)
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"
    

    协程的优势

    • 轻量级,占用更少的系统资源

    • 更高的执行效率

    • 挂起函数较于实现Runnable或Callable接口更加方便可控

    • kotlin.coroutine 核心库的支持,让编写异步代码更加简单

    启动协程

    launch:job 简单使用示意
    GlobalScope 单例对象 可以直接调用 launch 开启协程
    创建一个不会阻塞当前线程、没有返回结果的 Coroutine,但会返回一个 Job 对象,可以用于控制这个 Coroutine 的执行和取消,返回值为Job。

    // 默认在后台执行,可指定线程
    GlobalScope.launch {
        LogUtil.i("zz------${Thread.currentThread().name}")
    }
    //LogUtil: i: zz------DefaultDispatcher-worker-1
    

    runBlocking:T顶层函数

    创建一个会阻塞当前线程的Coroutine,常用于单元测试的场景,开发中一般不会用到
    runBlocking {}是创建一个新的协程同时阻塞当前线程,直到协程结束。这个不应该在协程中使用,主要是为main函数和测试设计的

    fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
        launch { // launch new coroutine in background and continue
            delay(1000L)
            println("World!")
        }
        println("Hello,") // main coroutine continues here immediately
        delay(2000L)      // delaying for 2 seconds to keep JVM alive
    }
    

    async/await:Deferred
    async 返回的 Coroutine 多实现了 Deferred 接口,简单理解为带返回值的launch函数
    async {}可以实现与 launch builder 一样的效果,在后台创建一个新协程,唯一的区别是它有返回值,因为async {}返回的是 Deferred 类型。
    获取async {}的返回值需要通过await()函数,它也是是个挂起函数,调用时会挂起当前协程直到 async 中代码执行完并返回某个值。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_demo2)
        testAsync()
    }
    
    private fun testAsync() = GlobalScope.launch {
        val deferred = async(Dispatchers.IO) {
            delay(3000L)
            "Show Time"
        }
        // 此处获取耗时任务的结果,我们挂起当前协程,并等待结果
        val result = deferred.await()
        //挂起协程切换至UI线程 展示结果
        withContext(Dispatchers.Main) {
            tvText.text = result
        }
    }
    

    async和await是两个函数,这两个函数在我们使用过程中一般都是成对出现的。
    async用于启动一个异步的协程任务,await用于去得到协程任务结束时返回的结果,结果是通过一个Deferred对象返回的。

    挂起函数

    suspend 修饰的方法称之为挂起函数,有标记和提醒作用
    挂起函数只能在协程或者另外一个挂起函数中使用,在协程中挂起函数结束后自动切回协程主线程
    挂起,就是一个稍后会被自动切回来的线程调度操作。

    // 设置主线程线程环境
    GlobalScope.launch(Dispatchers.Main) {
        // suspend,挂起函数,方法内切线程
        ioFun1()
        // 自动回到协程主线程,上面指定主线程
        uiFun1()
    }
    
    /*
    suspend 修饰的方法称之为挂起函数,有标记和提醒作用
       挂起函数只能在协程或者另外一个挂起函数中使用,在协程中挂起函数结束后自动切回“协程主线程”
    */
    private suspend fun ioFun1() {
        // 切换线程
        withContext(Dispatchers.IO) {
            LogUtil.i("zz------ioFun1--${Thread.currentThread().name}")
        }
    }
    
    private fun uiFun1() {
        LogUtil.i("zz------uiFun1--${Thread.currentThread().name}")
    }
    I/LogUtil: i: zz------ioFun1--DefaultDispatcher-worker-1
    I/LogUtil: i: zz------uiFun1--main
    

    取消协程

    未取消协程的执行

    val launch = GlobalScope.launch {
        LogUtil.i("launch------${Thread.currentThread().name}")
        // 耗时任务
        delay(5000)
        LogUtil.i("delay------${Thread.currentThread().name}")
    }
    //执行结果:
    I/LogUtil: i: launch------DefaultDispatcher-worker-1
    I/LogUtil: i: delay------DefaultDispatcher-worker-1
    

    取消协程后的执行

    val launch = GlobalScope.launch {
        LogUtil.i("launch------${Thread.currentThread().name}")
        // 耗时任务
        delay(5000)
        LogUtil.i("delay------${Thread.currentThread().name}")
    }
    // 取消协程
    launch.cancel()
    //执行结果
    I/LogUtil: i: launch------DefaultDispatcher-worker-1
    

    从日志的输入情况来看,协程取消后其后续的代码不在执行.

    线程的切换

    // 开启协程
    GlobalScope.launch {
        LogUtil.i("launch------${Thread.currentThread().name}")
        // 切换到主线程
        withContext(Dispatchers.Main) {
            LogUtil.i("launch------${Thread.currentThread().name}")
        }
    }
    
    I/LogUtil: i: launch------DefaultDispatcher-worker-1
    I/LogUtil: i: launch------main
    

    从日志的输出结果可以看到线程从子线程切换到了主线程中.
    使用GlobalScope.launch函数开启协程。在其中,使用withContext(Dispatchers.Main)将线程切换到主线程

    CoroutineDispatcher ,调度器

    协程调度器,决定协程所在的线程或线程池。它可以指定协程运行于特定的一个线程、一个线程池或者不指定任何线程(这样协程就会运行于当前线程)。coroutines-core中 CoroutineDispatcher 有两种标准实现 CommonPool 和 Unconfined,Unconfined 就是不指定线程。

    launch函数定义中的DefaultDispatcher实际上就是CommonPool,CommonPool是一个协程调度器,其指定的线程为共有的线程池。而且 CoroutineDispatcher 实现了 CoroutineContext 接口,所以才能直接指定context: CoroutineContext = DefaultDispatcher,实际上,协程上下文中的元素都实现了 CoroutineContext 接口
    Kotlin内置了四种调度器:

    • Dispatchers.Default
      默认的调度器,基于JVM上的共享线程池,最大线程数为CPU核心数。
    • Dispatchers.IO
      专为IO操作设计的调度器,默认最大线程数为64与CPU核心数的较大值。
    • Dispatchers.Main
      Android中的主线程,UI主线程调度器。
    • Dispatchers.Unconfined
      无限制调度器。在第一个挂起点之前,在调用它的线程中执行;之后由该挂起函数决定。

    GlobalScope

    GlobalScope是一个特殊的全局CoroutineScope,它不与任何Job绑定。GlobalScope只应该使用在生命周期与整个应用程序相同、且不被取消的协程中。

    CoroutineContext

    CoroutineContext,协程上下文,是一些元素的集合,主要包括 Job 和 CoroutineDispatcher 元素,可以代表一个协程的场景。
    EmptyCoroutineContext 表示一个空的协程上下文。

    Job & Deferred

    Job,任务,封装了协程中需要执行的代码逻辑。Job 可以取消并且有简单生命周期,它有三种状态

        //当 Job 处于活动状态时为 true
        //如果 Job 未被取消或没有失败,则均处于 active 状态
        public val isActive: Boolean
    
        //当 Job 正常结束或者由于异常结束,均返回 true
        public val isCompleted: Boolean
    
        //当 Job 被主动取消或者由于异常结束,均返回 true
        public val isCancelled: Boolean
    
        //启动 Job
        //如果此调用的确启动了 Job,则返回 true
        //如果 Job 调用前就已处于 started 或者是 completed 状态,则返回 false 
        public fun start(): Boolean
    
        //用于取消 Job,可同时通过传入 Exception 来标明取消原因
        public fun cancel(cause: CancellationException? = null)
    
        //阻塞等待直到此 Job 结束运行
        public suspend fun join()
    
        //当 Job 结束运行时(不管由于什么原因)回调此方法,可用于接收可能存在的运行异常
        public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
    复制代码
    

    Coroutine builders

    launch函数属于协程构建器 Coroutine builders,Kotlin 中还有其他几种 Builders,负责创建协程

    相关文章

      网友评论

          本文标题:八,Kotlin协程详解

          本文链接:https://www.haomeiwen.com/subject/tiiotltx.html