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

简介:

上一篇的kotlin讲到了协程的启动、等待和取消,这一篇对kotlin协程部分内容的补充。

挂起函数

   接触到协程之后出现的一个新型的函数,以特殊修饰符suspend修饰的函数被称为挂起函数。挂起函数只能在协程中和其他挂起函数中调用,不能在其他部分使用。并且要启动一个协程,挂起函数是必须的,为了验证可以查看上一篇中提到的launch源码。

/**
 * launch实现了Job接口
 * @param context 协程上下文,默认值是 [DefaultDispatcher].
 * @param start 协程启动方式,默认值是 [CoroutineStart.DEFAULT].
 * @param parent 显示指出父Job, 通过[context]重写job.
 * @param block 具体协程的代码.
 */
public actual fun launch(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    parent: Job? = null,
    block: suspend CoroutineScope.() -> Unit
): Job {
    ...
}

   可以看到第四个参数block是一个挂起函数,当将一个lambda表达式传给launch时,它就是一个挂起函数。不过这一部分需要引入另一个协程启动函数async,它的源码和launch很类似,不过async是返回了一个Deferred对象,而Deferred又是继承自Job。

public actual fun <T> async(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    parent: Job? = null,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    ...
}

   为什么已经有了一套Job-launch-join还需要Deferred-async-await,问题的关键就在Defered虽说继承于Job,但是他略有不同:

public actual interface Deferred<out T> : Job {
...
    public actual suspend fun await(): T
}

  从上面的代码中明显的看出,Deferred继承Job的基础上增加了out参数T(即有输出),而await函数的作用就是挂起一个协程并返回最后的执行结果,而join函数并没有返回这一项。如果挂起函数是有返回值的,那协程的启动和挂起当然要选择async+await的组合。下面提供一个例子,最后输出的结果是3:

fun main(args: Array<String>) {
    runBlocking<Unit> {
        val one = async(coroutineContext) { job1() }
        val two = async(coroutineContext) { job2() }
        println("结果是:${one.await() + two.await()}")
    }
}
suspend fun job1(): Int {
    return 1
}
suspend fun job2(): Int {
    return 2
}

Job与Deferred的对比

  已经学习到了两种协程启动的方式,但是他们是有着多种的状态和之间的切换,其实源代码中作者已经把这些都罗列好了,只需要复制粘贴就有了下面的几小结。

Job的几种状态

| 状态 | [isActive] | [isCompleted] | [isCancelled] |
| --------------------------------------- | ---------- | ------------- | ------------- |
| New (optional initial state) | false | false | false |
| Active (default initial state) | true | false | false |
| Completing (optional transient state) | true | false | false |
| Cancelling (optional transient state) | false | false | true |
| Cancelled (final state) | false | true | true |
| Completed (final state) | false | true | false |

Job状态的切换流程

这里写图片描述

Deferred的几种状态

| 状态 | [isActive] | [isCompleted] | [isCompletedExceptionally] | [isCancelled] |
| --------------------------------------- | ---------- | ------------- | -------------------------- | ------------- |
| New (optional initial state) | false | false | false | false |
| Active (default initial state) | true | false | false | false |
| Completing (optional transient state) | true | false | false | false |
| Cancelling (optional transient state) | false | false | false | true |
| Cancelled (final state) | false | true | true | true |
| Resolved (final state) | false | true | false | false |
| Failed (final state) | false | true | true | false |

Deferred状态的切换流程

这里写图片描述

调度和线程

   协程上下文包括一个协程调度程序CoroutineDispatcher,他可以指定由哪个线程来执行协程。调度器可以将协程调度到一个线程池,限制在特定的线程中;也可以不做任何限制,让它无约束运行。要去理解这句话,就必须参考launch或async构造函数中的第一个参数(可见第一章)。

runBlocking {
    val jobs = arrayListOf<Job>()
    jobs += async(Unconfined) {  // 1
        println("Unconfined is worked in ${Thread.currentThread()}")
    }
    jobs += async(coroutineContext) { // 2
        println("coroutineContext is worked in ${Thread.currentThread()}")
    }
    jobs += async(CommonPool) {  // 3
        println("CommonPool is worked in ${Thread.currentThread()}")
    }
    jobs += async(newSingleThreadContext("newThread")) { // 3
        println("newThread is worked in ${Thread.currentThread()}")
    }
    jobs.forEach { it.join() }
}
  • 注释1,使用的Unconfined,一种无限制上下文,协程会在当前调用的栈中执行直到第一次挂起,挂起之后该协程可以被任意一个线程调用以重启;
  • 注释2,coroutineContext继承CoroutineContext,在主线程中执行;
  • 注释3,CommonPool,上一篇讲过,直接继承于CoroutineDispatcher,在内部创建了一个ExecutorService类型线程池;
  • 注释4,新建一个ThreadPoolDispatcher类型的包含一个线程的协程上下文,他可以调度协程到一个固定大小的线程池中。
  • 有了上面的解释,程序运行之后的结果也能猜出个几分:
    这里写图片描述

协程的内部机制

   协程完全通过编译技术实现(不需要来自VM或OS端的支持),挂起通过代码来生效。基 本上,每个挂起函数(优化可能适用,但我们不在这里讨论)都转换为状态机,其中的状态 对应于挂起调用。刚好在挂起前,下一状态与相关局部变量等一起存储在编译器生成的类的 字段中。在恢复该协程时,恢复局部变量并且状态机从刚好挂起之后的状态进行。
   挂起的协程可以作为保持其挂起状态与局部变量的对象来存储和传递。这种对象的类型是 Continuation,而这里描述的整个代码转换对应于经典的延续性传递风格(Continuationpassing style)。因此,挂起函数有一个Continuation类型的额外参数作为高级选项。

总结

   协程的一些用法基本上可以先总结这些了,目前协程的一些api仍然是实验中的,不过也是出现了很多新名词。

  • runBlocking函数,主要用于桥接阻塞代码(如Thread)和挂起风格非阻塞(如delay)代码,这样可以保证编码风格的统一;
  • launch和async函数,都是启动协程的函数,async有返回值,分别返回Job和Deferred对象;
  • CoroutineDispatcher函数,作为CoroutineContext的子类,用于对协程的调度。
目录
相关文章
|
3月前
|
安全 Java Android开发
Kotlin入门实用开发技巧与注意事项
本文源自公众号“AntDream”。Kotlin是由JetBrains开发的现代编程语言,自2017年成为Android官方开发语言后迅速流行。本文作者分享了Kotlin的实用技巧,包括变量声明、空安全、扩展函数等,帮助初学者避免常见问题。
77 15
|
28天前
|
Java 编译器 测试技术
Kotlin31 协程如何与 Java 进行混编?
Kotlin31 协程如何与 Java 进行混编?
28 2
Kotlin31 协程如何与 Java 进行混编?
|
19天前
|
Java 编译器 Kotlin
Kotlin入门笔记1 - 数据类型
Kotlin入门笔记1 - 数据类型
67 15
|
2月前
|
Java 网络架构 Kotlin
kotlin+springboot入门级别教程,教你如何用kotlin和springboot搭建http
本文是一个入门级教程,介绍了如何使用Kotlin和Spring Boot搭建HTTP服务,并强调了Kotlin的空安全性特性。
90 7
kotlin+springboot入门级别教程,教你如何用kotlin和springboot搭建http
|
2月前
|
存储 前端开发 测试技术
Android kotlin MVVM 架构简单示例入门
Android kotlin MVVM 架构简单示例入门
40 1
|
2月前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
38 1
|
4月前
|
调度 开发者 UED
Kotlin 中的协程是什么?
【8月更文挑战第31天】
345 0
|
6月前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
70 1
|
6月前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
143 2
|
7月前
|
移动开发 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美融合
【5月更文挑战第25天】 在移动开发的世界中,性能和响应性是衡量应用质量的关键指标。随着Kotlin的流行和协程的引入,Android开发者现在有了更强大的工具来提升应用的性能和用户体验。本文深入探讨了Kotlin语言如何与协程相结合,为Android应用开发带来异步处理能力的同时,保持代码的简洁性和可读性。我们将通过实际案例分析,展示如何在Android项目中实现协程,以及它们如何帮助开发者更有效地管理后台任务和用户界面的流畅交互。