前言
Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async
与 await
在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的 挂起函数 概念为异步操作提供了比 future 与 promise 更安全、更不易出错的抽象。
kotlinx.coroutines
是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启用高级协程的原语,包括 launch
、 async
等等。
如需了解其他kotlin用法,可查看如下:
Android Kotlin实战之高阶使用泛型扩展协程懒加载详解_蜗牛、Z的博客-CSDN博客
Android kotlin在实战过程问题总结与开发技巧详解_kotlin 同步锁_蜗牛、Z的博客-CSDN博客
Kotlin语法详解与实践教程,区分JAVA以及如何闭坑_kotlin mapof可以嵌套 to_蜗牛、Z的博客-CSDN博客
什么是协程suspend
suspend的字面含义是暂停、挂起的意思。在kotlin中,代码执行到 suspend 函数的时候会『挂起』,并且这个『挂起』是非阻塞式的,它不会阻塞你当前的线程,挂起的定位: 暂时切走,稍后再切回来,和java的Thread.sleep()不一样,一个是阻塞,一个是等待,类似wait。
阻塞的基本原理一直占用CUP,不让出如果sleep,挂起的是将自己的cup时间让出去,等待重新分配。这样就很好理解了。用最直观的理解是一种异步操作
协程方法的调用
如果当前方法被suspend修饰了,如果不是通过runblock或者coroutines
库调用,那么调用链上的所有函数都要背suspend修饰
1.修饰方法
suspend fun main() {
delay(1000)
println("hello")
}
又因为我们在业务中,很难会获取到main方法,所以无法通过main方法在源头解决协程的调用
2.借助工具类
2.1同步方法runBlocking
runBlocking是阻塞调用,如果通过它来处理协程,那么只有当runBlocking的方法体中执行完,才会往下执行,会一直霸占cpu,直到运行完才会释放。
fun main() {
runBlocking {
delay(1000)
println("hello---block")
}
println("hello")
}
2.2异步库kotlinx.coroutines
异步库的调用是先挂起线程代码,继续执行,等下面执行完才会又回到协程函数执行
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
println("suspend start!") // 在延迟后打印输出
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("suspend end!") // 在延迟后打印输出
}
println("sleep start,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
println("sleep end,") // 协程已在等待时主线程还在继续
}
通过以上日志分析,协程提都不会被执行,被挂起了,先执行了下面的代码,遇到sleep函数,形成阻塞,协程又获取到cpu的时间,开始执行协程代码快,执行完,sleep阻塞完成又继续执行。
coroutines
库的使用
kotlinx.coroutines是填补了kotlin在Java中的缺失,也提供了异步处理协程的问题。
依赖库:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
使用
1、异步挂起协程
GlobalScope.launch { // 在后台启动一个新的协程并继续
//协程代码调用处
}
2、延迟
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
}
直接调用delay,设置需要阻塞延迟的时间
3、工作对象Job
launch会返回一个对象Job,job就是当前的任务,如果你接受了这个Job,那么代码块将不会执行,需要你手动调用
fun main() {
val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用
delay(1000L)
println("World!")
}
println("Hello,")
runBlocking {
job.join() // 等待直到子协程执行结束
}
}
job的join只能在阻塞方法中调用,或者suspend方法体,不能在
GlobalScope.launch {
job.join()
}
调用,否则不会执行当前代码块。因为launch已返回了一个job,当前job没有执行,所以,改job一直处于一个未执行状态。
也可以取消:job.cancel()
协程作用域:launch{}
GlobalScope与CoroutineScope对比
八股文显示,前者比后者快,通过源码分析,GlobalScope是继承了CoroutineScope,是对CoroutineScope的封装,提供一个静态函数。
结构化的并发
协程的实际使用还有一些需要改进的地方。 当我们使用 GlobalScope.launch
时,我们会创建一个顶层协程。虽然它很轻量,但它运行时仍会消耗一些内存资源。有一个更好的解决办法。我们可以在代码中使用结构化并发。 我们可以在执行操作所在的指定作用域内启动协程, 而不是像通常使用线程(线程总是全局的)那样在 GlobalScope 中启动.
fun main() = runBlocking { // this: CoroutineScope
launch { // 在 runBlocking 作用域中启动一个新协程
delay(1000L)
println("World!")
}
println("Hello,")
}
作用域构建器
除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束.
runBlocking 与 coroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。 主要区别在于,runBlocking 方法会阻塞当前线程来等待, 而 coroutineScope 只是挂起,会释放底层线程用于其他用途。 由于存在这点差异,runBlocking 是常规函数,而 coroutineScope 是挂起函数
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking 1")
}
coroutineScope { // 创建一个协程作用域
launch {
delay(500L)
println("Task from nested launch 2")
}
delay(100L)
println("Task from coroutine scope 3") // 这一行会在内嵌 launch 之前输出
}
println("Coroutine scope is over 4") // 这一行在内嵌 launch 执行完毕后才输出
}
挂起 :GlobalScope.async
GlobalScope的async异步挂起,支持返回值,且不能单独直接使用,需在runBlocking体内执行。
fun main()= runBlocking {
println("current thread 1")
//launch启动后台调度线程,并且不堵塞当前线程
val deferred = GlobalScope.async {
println("async thread 2=")
delay(1000)
println("async end 3")
//需要通过标签的方式返回
return@async "123"
}
println("current thread end 4")
val result = deferred.await()
println("result 5= $result")
//当前线程休眠以便调度线程有机会执行
Thread.sleep(3000)
}
协程上下文与调度器
协程上下文包含一个 协程调度器 (参见 CoroutineDispatcher)它确定了相关的协程在哪个线程或哪些线程上执行。协程调度器可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。
launch { // 运行在父协程的上下文中,即 runBlocking 主协程
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // 不受限的——将工作在主线程中
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // 将会获取默认调度器
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // 将使它获得一个新的线程
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
调度器类型如下:
public actual object Dispatchers {
@JvmStatic
public actual val Default: CoroutineDispatcher = DefaultScheduler
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
@JvmStatic
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
@JvmStatic
public val IO: CoroutineDispatcher = DefaultIoScheduler
@DelicateCoroutinesApi
public fun shutdown() {
DefaultExecutor.shutdown()
// Also shuts down Dispatchers.IO
DefaultScheduler.shutdown()
}
}
调试协程与线程
协程可以在一个线程上挂起并在其它线程上恢复。 如果没有特殊工具,甚至对于一个单线程的调度器也是难以弄清楚协程在何时何地正在做什么事情。
启动方式
runBlocking | 创建新的协程,运行在当前线程上,所以会堵塞当前线程,直到协程体结束 |
GlobalScope.launch | 启动一个新的线程,在新线程上创建运行协程,不堵塞当前线程 |
GlobalScope.asyn | 启动一个新的线程,在新线程上创建运行协程,并且不堵塞当前线程,支持 通过await获取返回值 |