目录
1.协程是什么
2.协程的作用
3.协程的架构
4.协程的原理
5.协程的创建
6.协程的调度
7.协程的取消&超时处理
8.与Go和Java相比较
协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码
简单的来说:我们可以以同步的方式去编写异步执行的代码
1.协程可以让异步代码同步化
2.协程可以降低异步程序的设计复杂度
基础设施层:标准库的协程API,主要对协程提供了概念和语义上最基本的支持;
业务框架层 kotlin.coroutines:协程的上层框架支持,基于标准库实现的封装,也是我们日常开发使用的协程扩展库。
协程的概念最核心的点就是函数或者程序块能够被挂起,并且能够再在挂起的位置恢复。协程通过主动让出运行权来实现协作,程序自己处理挂起和恢复来实现程序执行流程的协作调度。因此它本质上就是在规范程序控制流程的机制。
协程使用同步的方式完成异步任务,而且代码简洁,这也是Kotlin协程的魅力所在。之所有可以用看起来同步的方式写异步代码,关键在于请求函数getUserSuspend()是一个挂起函数,被suspend关键字修饰
从上面的协程原理图解中可以看出,耗时阻塞的操作并没有减少,只是交给了另一个IO线程。userApi.getUserSuspend(“suming”)真正执行的时候会切换到IO线程中执行,获取结果后最后恢复到主线程上,然后继续执行剩下的流程。
将业务流程原理拆分得更细致一点,在主线程中创建协程A中执行整个业务流程,如果遇到异步调用任务则协程A被挂起,切换到IO线程中创建子协程B,获取结果后再恢复到主线程的协程A上,然后继续执行剩下的流程。
协程虽然不能脱离线程而运行,但可以在不同的线程之间切换,而且一个线程上可以运行一个或多个协程。
kotlin协程基于Thread相关API的封装,让我们不用过多关心线程也可以方便地写出并发操作,这就是Kotlin的协程。协程的好处本质上和其他线程api一样,使用方便。
在 Android 平台上,协程有两个主要使用场景:
1.线程切换,保证线程安全。
2.处理耗时任务(比如网络请求、解析JSON数据、从数据库中进行读写操作等)。
在 Android 项目中使用协程,请将以下依赖项添加到应用的 build.gradle 文件中:
dependencies {
//协程标准库
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"
//协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
//协程Android支持库,提供安卓UI调度器
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
}
创建协程的三种方式:
使用 runBlocking 顶层函数创建
使用 GlobalScope 单例对象创建
自行通过 CoroutineContext 创建一个 CoroutineScope 对象
举例
runBlocking {
Log.i(TAG, "启动协程runBlocking.")
}
GlobalScope.launch {
Log.i(TAG, "启动协程launch.")
}
val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
Log.i(TAG, "启动协程launch.")
}
比较
方法一:通常适用于单元测试的场景,而业务开发中不会用到这种方法,因为它是线程阻塞的。
方法二:和使用 runBlocking 的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法,因为它的生命周期会只受整个应用程序的生命周期限制,且不能取消。
方法三:是比较推荐的使用方法,我们可以通过 context 参数去管理和控制协程的生命周期(这里的 context 和 Android 里的不是一个东西,是一个更通用的概念,会有一个 Android 平台的封装来配合使用)。
Kotlin 提供了四个调度器:
Dispatchers.Default
Dispatchers.Main
Dispatchers.Unconfined
Dispatchers.IO
注意:当启动协程时如果不带参数的情况下,它从外部的协程作用域继承上下文和调度器
举例
GlobalScope.launch(Dispatchers.Main) {
Log.i(TAG, "主线程调度器.")
}
GlobalScope.launch(Dispatchers.Default) {
Log.i(TAG, "默认调度器.")
}
GlobalScope.launch(Dispatchers.Unconfined) {
Log.i(TAG, "任意调度器.")
}
GlobalScope.launch(Dispatchers.IO) {
Log.i(TAG, "IO调度器.")
}
每一个协程创建时,都会生成一个 Job 实例,这个实例是协程的唯一标识,负责管理协程的生命周期。当协程创建、执行、完成、取消时,Job 的状态也会随之改变,通过检查 Job 对象的属性可以追踪到协程当前的状态
Job 一共包含六个状态:
新创建 New
活跃 Active
完成中 Completing
已完成 Completed
取消中 Cancelling
已取消 Cancelled
Job生命周期
通常来说,Job 的生命周期会经过四个状态:New ——> Active——> Completing ——>Completed。
协程在 Active 和 Completing 时可以响应取消命令。如果收到了取消命令,协程会马上经过 Cancelling ——>Cancelled 生命周期。
Job超时处理
使用 withTimeout 或 withTimeoutOrNull 可以指定 Job 任务的超时时间。两者的区别在于:
withTimeout 函数会在超时后抛出一个超时异常 TimeoutCancellationException
withTimeoutOrNull 函数会在超时后返回一个 null 值
举例
runBlocking {
try {
withTimeout(500) {
launch {
delay(1000)
}
}
} catch (e: TimeoutCancellationException) {
e.printStackTrace()
}
}
输出:2023-03-21 20:41:27.078 28730-28730/com.example.kotlindemo W/System.err: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 500 ms
runBlocking {
val result = withTimeoutOrNull(500) {
launch {
delay(1000)
}
}
Log.i(TAG, "withTimeoutOrNull: " + result)
}
输出:2023-03-21 20:43:35.887 28893-28893/com.example.kotlindemo I/Coroutine: withTimeoutOrNull: null
Go 中的协程
Go 协程(Goroutine)是与其他函数同时运行的函数。可以认为 Go 协程是轻量级的线程,由 Go 运行时来管理。
在函数调用前加上 go 关键字,这次调用就会在一个新的 goroutine 中并发执行。当被调用的函数返回时,这个 goroutine 也自动结束。听起来像 C# 中的 Task。
需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
Go 协程(Goroutine)之间通过信道(channel)进行通信,简单的说就是多个协程之间通信的管道。信道可以防止多个协程访问共享内存时发生资源争抢的问题。
举例
package main
import ( "fmt"
"time" )
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
Java
Java 原生语言暂时不支持协程的,但是在使用Java进行Android项目开发时可以使用以下方法实现类似协程的作用。
缺点: