Kotlin从入门到放弃(三)——协程

简介: 引言 这篇主要是将以下kotlin里面的协程,当然这个概念已经随着kotlin的文档被广泛得知了,不过还是用大量代码记录一下吧一、概念   Coroutine,翻译为协程,意思为各个子任务程协作运行。

引言

这篇主要是将以下kotlin里面的协程,当然这个概念已经随着kotlin的文档被广泛得知了,不过还是用大量代码记录一下吧

一、概念

   Coroutine,翻译为协程,意思为各个子任务程协作运行。由此可以联想到Java常用的线程概念,java中的线程Thread最终启动的地方是JVM核心层,也就是说java的线程其实本质也是和硬件有关(这是当然的)。而多线程任务在并发的情况下会出现阻塞的情况,协程提供了挂起这种方法去避免阻塞线程并用更廉价更可控的操作替代线程阻塞。

二、入门

   协程是由程序直接实现的,是一种轻量级线程,kotlin也为此提供了标准库和额外的实验库。标准库为kotlin.coroutines.experimental(写作时使用kotlin-1.20版本),可见仍然还是一个实验性功能。其实协程的例子能在网上找到很多,就不一一去分析了,这里以标准库实现的斐波那契方法为例讲解一下。

val fibonacciSeq = buildSequence {
        var a : Long = 0
        var b : Long = 1
        yield(1)           // 1
        while (true) {
            yield(a + b)   // 2
            val tmp = a + b
            a = b
            b = tmp
            print(tmp.toString() + " ") // 3
        }
    }
println(fibonacciSeq.take(10).toList()) // 4

   上面的fibonacciSeq函数实现了斐波那契数列,但是不同于java语法写的方法这里多了一个buildSequence,buildSequence源码如下:

/**
 * Builds a [Sequence] lazily yielding values one by one.
 *
 * @see kotlin.sequences.generateSequence
 *
 * @sample samples.collections.Sequences.Building.buildSequenceYieldAll
 * @sample samples.collections.Sequences.Building.buildFibonacciSequence
 */
@SinceKotlin("1.1")
public fun <T> buildSequence(builderAction: suspend SequenceBuilder<T>.() -> Unit): Sequence<T> = Sequence { buildIterator(builderAction) }

   从官方的注释中可以知道,这是创建了一个惰性的序列,也就是函数fibonacciSeq是一个创建无穷惰性的斐波那契数列。注释1处的yield(1)作用就是输出1,yield的作用可以参考Python语言的相当于return;同样注释2处也同理;注释3和4处均是输出,不同的是4处可以主动取出有限的数列,两者的输出作为对比去理解yield在此处的return作用。
输出结果
   上图可以明显看出注释3处少输出了一个元素,这是因为yield在第10次直接返回,跳出了while循环。构建这种无限序列(后面还会讲)是协程的一个主要特点,不过可能从这里很难看出它的概念(子任务),理解任务的调度可以引入kotlin提供的一个额外Coroutine库——kotlinx.coroutines.experimental,显而易见也是试验阶段。

三、实践

3.1 启动协程

   使用kotlinx库就需要gradle了(当然其他的如Maven也是可以的,手动滑稽),版本选择和导入不再赘述,还是直接上代码。

    // 在Common线程池启动协程
    launch(CommonPool) { 
        delay(2000L)    // 1
        println("Hello")
    }
    println("World")
    Thread.sleep(3000L) // 2
    // 在主线程中启动协程
    runBlocking<Unit> {
        println("T0")
        launch(CommonPool) {
            println("T1")
            delay(3000L)
            println("T2 Hello")
        }
        println("T3 World")
        delay(5000L)
        println("T4")
    }

   此处将两个函数写在一处好做对比,1处的delay函数(非阻塞)是一个suspend(挂起)函数,在这里的作用相当于2处的Thread.sleep(阻塞),但是delay必须在协程中或者挂起函数中使用,但是lauch是在CommonPool共享线程池中创建协程并不是主线程,所以不能使用,解决的办法就是使用runBlocking——桥接普通阻塞代码和挂起风格的非阻塞代码,即可以在里面启动协程,使用挂起函数和常用的阻塞方法。

3.2 取消协程

   有启动自然就有取消操作,使用cancel函数,当然这里也是有需要注意的点。

runBlocking {
        val job = launch {
            repeat(1000) {
                i -> println("job sleeping $i ... CurrentThread: ${Thread.currentThread()}")
                delay(500L)
            }

        }

        val job1 = launch {
            var nextTime = 0L
            var i = 0
            while (i < 20) {
                var currentTime = System.currentTimeMillis();
                if (currentTime >= nextTime) {
                    println("job1 sleeping ${i++} ... CurrentThread: ${Thread.currentThread()}")
                    nextTime = currentTime + 500L
                }
            }
        }
        delay(1900L)
        println("Job is alive: ${job.isActive}; Job iscompleted: ${job.isCompleted}")
        println("Job1 is alive: ${job1.isActive}; Job1 iscompleted: ${job1.isCompleted}")
        val b1 = job.cancel()
        val c1 = job1.cancel()
        println("job cancel: $b1 and job1 cancel: $c1")
        delay(1300L)
        println("Job is alive: ${job.isActive}; Job iscompleted: ${job.isCompleted}")
        println("Job1 is alive: ${job1.isActive}; Job1 iscompleted: ${job1.isCompleted}")
        delay(30000L)
        val b2 = job.cancel()
        val c2 = job1.cancel()
        println("job cancel: $b2 and job1 cancel: $c2")
        println("Job is alive: ${job.isActive}; Job iscompleted: ${job.isCompleted}")
        println("Job1 is alive: ${job1.isActive}; Job1 iscompleted: ${job1.isCompleted}")
    }

   同样是两个协程job和job1,job是一个可重复1000次但会被挂载的协程而job1是一个有时间间隔循环20次的协程,这里就不截取输出的结果了因为比较长。协程job由于使用了delay函数挂起,在调用了cancel之后协程实现了真正的停止;协程job1存在循环计算并没有挂起操作,即使调用了cancel,协程状态也变为停止,但是循环操作仍然在继续,这种情况下取消就会失效。那遇到第二种情况该怎么办嘞,联系到上面讲过的yield函数(注意这里的yield函数是kotlinx包中的和上面那个重名),写在适当的地方就可以了(话说还不如直接用delay)。

3.3 等待协程

   之前的代码中实现的功能不同,但是有个共同的特点,那就是主线程主动挂起或者阻塞等待协程里面的代码执行,这在实际使用中肯定是不可取。kotlinx提供了join函数,可以使主线程等待协程执行完。

runBlocking {
    var c1 = launch(CommonPool) {
        delay(1000L)
        println("Coroutine 1")
    }
    var c2 = launch {
        delay(1000L)
        println("Coroutine 2")
    }
    c1.join() // 1
    c2.join() // 2
    println("the main")
}

   join函数也是一个挂起函数源码解析放在之后吧,如果没有1和2处的代码,整个程序的运行结果只有“the main”,而现在的执行结果如下图:
这里写图片描述
   上图的目的就是为了说明协程的执行也是无序的。

3.4 CommonPool线程池

   官方的解释是这样的Represents common pool of shared threads as coroutine dispatcher for compute-intensive tasks,机器翻译一下就是将共享线程池作为一个协程来调度计算密集型任务。注释1处会尝试新建一个ForkJoinPool(一个可执行ForkJoinTask的ExcuteService,采用工作窃取算法:所有在池中的线程尝试去执行其他线程创建的子任务,这样很少有线程处于空闲状态,更加高效);如果不可用,就是用Executors来创建一个普通的线程池,创建过程在注释3处。

    private fun createPool(): ExecutorService {
        val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
            ?: return createPlainPool()   // 1
        if (!usePrivatePool) {
            Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
                ?.let { return it }
        }
        Try { fjpClass.getConstructor(Int::class.java).newInstance(defaultParallelism()) as? ExecutorService }
            ?. let { return it }
        return createPlainPool()  // 2
    }

    private fun createPlainPool(): ExecutorService {
        val threadId = AtomicInteger()
        return Executors.newFixedThreadPool(defaultParallelism()) {
            Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }  // 3
        }
    }

    private fun defaultParallelism() = (Runtime.getRuntime().availableProcessors() - 1).coerceAtLeast(1)

四、小结

   协程的概念和基本操作讲的差不多了,对于经常接触Thread线程突然去理解协程还是有点障碍的,不过官方提供了很好的试验库去入门。协程也不是一两篇文章就能讲清楚的,应该还会有下一篇继续介绍。

目录
相关文章
|
1月前
|
安全 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美结合
【2月更文挑战第30天】在移动开发领域,性能优化和流畅的用户体验是关键。本文深入探讨了如何通过结合Kotlin语言和协程技术来提升Android应用的性能和响应能力。我们将分析Kotlin的优势,介绍协程的基本概念,并通过实际案例展示如何在应用中实现协程以简化异步编程,从而提供更加高效的解决方案。
|
1月前
|
移动开发 Java Android开发
构建高效Android应用:Kotlin与协程的完美融合
【2月更文挑战第25天】 在移动开发领域,性能优化和应用响应性的提升是永恒的追求。随着Android Jetpack组件库的不断丰富,Kotlin语言已经成为Android开发的首选。而Kotlin协程作为一种新的并发处理方案,它以轻量级线程的形式,为开发者提供了简洁高效的异步编程手段。本文将深入探讨Kotlin协程在Android应用中的实践运用,以及如何通过这种技术改善用户界面的流畅度和后台任务的处理能力,进而构建出更高效、更稳定的Android应用。
|
1月前
|
移动开发 调度 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【2月更文挑战第21天】随着移动开发技术的不断进步,Android平台正寻求更高效、更简洁的编程解决方案。Kotlin作为一种现代编程语言,以其简洁性和对函数式编程的支持赢得了开发者的青睐。而协程作为处理异步任务的强大工具,在提升应用性能方面显示出巨大潜力。本文将深入探讨Kotlin语言和协程技术如何相辅相成,帮助开发者构建更加流畅和高效的Android应用。
|
1月前
|
API 数据库 Android开发
构建高效安卓应用:Kotlin与协程的完美融合
【2月更文挑战第20天】在移动开发领域,性能优化和资源管理始终是开发者关注的焦点。随着Kotlin语言在Android平台的普及,协程作为其核心特性之一,提供了一种全新的异步编程范式。本文将深入探讨如何通过Kotlin协程提升安卓应用的性能,减少内存消耗,并实现流畅的用户体验。我们将分析协程的原理,展示其在实际应用中的效能,并提供实践指南,帮助开发者掌握这一强有力的工具。
14 0
|
1月前
|
安全 Java Android开发
构建高效Android应用:Kotlin与协程的完美融合
【2月更文挑战第15天】随着移动开发技术的不断进步,Android平台的开发者寻求更高效的编程解决方案以提升应用性能和用户体验。Kotlin作为官方推荐的开发语言,以其简洁性和功能安全性受到青睐。而协程,作为一种轻量级的线程管理机制,在异步编程和网络请求等方面展现出巨大潜力。本文将深入探讨Kotlin语言结合协程如何优化Android应用的性能,包括提高响应性、简化异步代码结构以及减少资源消耗等方面,旨在为开发者提供实战指南,帮助他们构建更加流畅和高效的Android应用。
|
2月前
|
机器学习/深度学习 数据挖掘 程序员
深入理解Python协程:提升并发编程效率基于Python的机器学习入门:从理论到实践
本文旨在探讨Python协程(Coroutine)的内部机制及其在并发编程中的应用。区别于传统的线程和进程,协程提供了一种更轻量级、高效的并发编程模式。通过深入分析协程的工作原理,本文将展示如何利用协程优化程序性能,实现高效的异步任务处理。我们将通过实例探讨协程的创建、事件循环的管理、以及与异步IO的集成,为读者提供一套完整的协程应用方案。此外,本文还将对比协程与其他并发模型(如多线程和多进程)的优劣,帮助读者全面理解协程在现代编程中的重要性。 在本文中,我们将深入探讨机器学习的核心概念,并通过Python实现其基础应用。不同于传统的技术文章摘要,我们希望通过一个故事性的引入,让读者感受到
|
6月前
|
安全 调度 数据库
Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(下)
Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(下)
43 0
|
6月前
|
Java Go Android开发
Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(上)
Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(上)
49 0
|
11月前
|
XML 算法 数据格式
Kotlin 协程 | CoroutineContext 为什么要设计成 indexed set?(一)
Kotlin 协程 | CoroutineContext 为什么要设计成 indexed set?(一)
64 0