最近刚刚接触到协程,网上也翻阅了大量其他作者的博文来看,总感觉越看越不透彻,所以特此记录自己对于kotlin协程的理解和认识,如果有误,望指正
目录
协程定义
官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
创建协程
kotlin 里没有 new ,自然也不像 JAVA 一样 new Thread,另外 kotlin 里面提供了大量的高阶函数,所以不难猜出协程这里 kotlin 也是有提供专用函数的。kotlin 中 GlobalScope 类提供了几个携程构造函数:
- launch - 创建协程
- async - 创建带返回值的协程,返回的是 Deferred 类
- withContext - 不创建新的协程,在指定协程上运行代码块
- runBlocking- 不是 GlobalScope 的 API,可以独立使用,区别是 runBlocking 里面的 delay 会阻塞线程,而 launch 创建的不会
kotlin 在 1.3 之后要求协程必须由 CoroutineScope 创建,CoroutineScope 不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器。比如 CoroutineScope.launch{} 可以看成 new Coroutine
1.launch:Job
这是最常用的用于启动协程的方式,它最终返回一个Job类型的对象,这个Job类型的对象实际上是一个接口,它包涵了许多我们常用的方法。下面先看一下简单的使用:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e(TAG, "主线程id:${mainLooper.thread.id}")
val job = GlobalScope.launch {
delay(6000)
Log.e(TAG, "协程执行结束 -- 线程id:${Thread.currentThread().id}")
}
Log.e(TAG, "主线程执行结束")
}
//Job中的方法
job.isActive
job.isCancelled
job.isCompleted
job.start() - 启动协程,除了 lazy 模式,协程都不需要手动启动
job.join() - 等待协程执行完毕
job.cancel() - 取消一个协程
job.cancelAndJoin() - 等待协程执行完毕然后再取消
执行的结果
E/test: 主线程id:2
E/test: 主线程执行结束
E/test: 协程执行结束 -- 线程id:24613
launch的源码:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
从上可以看出
从方法定义中可以看出,launch() 是CoroutineScope的一个扩展函数,CoroutineScope简单来说就是协程的作用范围。launch方法有三个参数:1.协程下上文;2.协程启动模式;3.协程体: block是一个带接收者的函数字面量,接收者是CoroutineScope
1.1协程上下文
上下文可以有很多作用,包括携带参数,拦截协程执行等等,多数情况下我们不需要自己去实现上下文,只需要使用现成的就好。上下文有一个重要的作用就是线程切换,Kotlin协程使用调度器来确定哪些线程用于协程执行,Kotlin提供了调度器给我们使用:
-
Dispatchers.Main:使用这个调度器在 Android 主线程上运行一个协程。可以用来更新UI 在UI线程中执行
-
Dispatchers.IO:这个调度器被优化在主线程之外执行磁盘或网络 I/O。在线程池中执行
-
Dispatchers.Default:这个调度器经过优化,可以在主线程之外执行 cpu 密集型的工作。例如对列表进行排序和解析 JSON。在线程池中执行。
-
Dispatchers.Unconfined:在调用的线程直接执行。
1.2启动模式
在Kotlin协程当中,启动模式定义在一个枚举类中:
public enum class CoroutineStart {
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED;
}
一共定义了4种启动模式,详情介绍:
- 缺省 默认为DEFAULT
- DEFAULT 立即等待被调度执行()
- ATOMIC 立即等待被调度执行,并且开始执行前无法被取消,直到执行完毕或者遇到第一个挂起点 - suspend
- UNDISPATCHED 立即在当前线程执行协程体内容
- LAZY 需要手动触发才会进入等待调度
这里的立即等待被执行就是当当前协程处于空闲状态下才会去执行
1.2.1 CoroutineStart .DEFAULT
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG,"start")
//new Coroutine 实例化协程
GlobalScope.launch{
Log.d(TAG,"work")
//delay(200)
launch(start = CoroutineStart.DEFAULT) {
Log.d(TAG,"pass")
}
Log.d(TAG,"over")
delay(300)
Log.d(TAG,"finish")
}
Log.d(TAG,"end")
}
2020-11-08 22:27:14.197 10613-10613/com.example.coroutinesdemo D/test: start
2020-11-08 22:27:14.236 10613-10613/com.example.coroutinesdemo D/test: end
2020-11-08 22:27:14.238 10613-10652/com.example.coroutinesdemo D/test: work
2020-11-08 22:27:14.241 10613-10652/com.example.coroutinesdemo D/test: over
2020-11-08 22:27:14.244 10613-10651/com.example.coroutinesdemo D/test: pass
2020-11-08 22:27:14.589 10613-10653/com.example.coroutinesdemo D/test: finish
1.2.2 CoroutineStart .UNDISPATCHED
GlobalScope.launch{
Log.d(TAG,"work")
//delay(200)
launch(start = CoroutineStart.UNDISPATCHED) {
Log.d(TAG,"pass")
}
Log.d(TAG,"over")
delay(300)
Log.d(TAG,"finish")
}
Log.d(TAG,"end")
}
2020-11-08 22:33:20.477 11325-11325/com.example.coroutinesdemo D/test: start
2020-11-08 22:33:20.513 11325-11325/com.example.coroutinesdemo D/test: end
2020-11-08 22:33:20.515 11325-11364/com.example.coroutinesdemo D/test: work
2020-11-08 22:33:20.519 11325-11364/com.example.coroutinesdemo D/test: pass
2020-11-08 22:33:20.520 11325-11364/com.example.coroutinesdemo D/test: over
2020-11-08 22:33:20.851 11325-11364/com.example.coroutinesdemo D/test: finish
1.2.3 CoroutineStart .LAZY
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG,"start")
//new Coroutine 实例化协程
GlobalScope.launch{
Log.d(TAG,"work")
//delay(200)
var job = launch(start = CoroutineStart.LAZY) {
delay(200)
Log.d(TAG,"pass")
}
//lazy需要手动触发,让其协程进入等待调度阶段 和DEFAULT效果一样
job.start()
//join会堵塞当前协程等待job协程执行完毕 和 UNDISPATCHED 有差别
//job.join()
Log.d(TAG,"over")
delay(300)
Log.d(TAG,"finish")
}
Log.d(TAG,"end")
}
job.start()
2020-11-08 22:43:51.526 12562-12562/com.example.coroutinesdemo D/test: start
2020-11-08 22:43:51.536 12562-12562/com.example.coroutinesdemo D/test: end
2020-11-08 22:43:51.537 12562-12602/com.example.coroutinesdemo D/test: work
2020-11-08 22:43:51.538 12562-12602/com.example.coroutinesdemo D/test: over
2020-11-08 22:43:51.781 12562-12606/com.example.coroutinesdemo D/test: pass
2020-11-08 22:43:51.845 12562-12603/com.example.coroutinesdemo D/test: finish
job.join()
2020-11-08 22:45:03.471 12693-12693/com.example.coroutinesdemo D/test: start
2020-11-08 22:45:03.511 12693-12693/com.example.coroutinesdemo D/test: end
2020-11-08 22:45:03.512 12693-12734/com.example.coroutinesdemo D/test: work
2020-11-08 22:45:03.766 12693-12735/com.example.coroutinesdemo D/test: pass
2020-11-08 22:45:03.783 12693-12737/com.example.coroutinesdemo D/test: over
2020-11-08 22:45:04.124 12693-12734/com.example.coroutinesdemo D/test: finish
job.join()会堵塞当前协程等待job协程执行完毕 后才会执行后面的代码
1.3协程体
协程体是一个用suspend关键字修饰的一个无参,无返回值的函数类型。被suspend修饰的函数称为挂起函数,与之对应的是关键字resume(恢复),注意:挂起函数只能在协程中和其他挂起函数中调用,不能在其他地方使用。
suspend函数会将整个协程挂起,而不仅仅是这个suspend函数,也就是说一个协程中有多个挂起函数时,它们是顺序执行的
总结一下:
- Kotlin 中规定:挂起函数只能在协程或者其他suspend函数中使用,其实就相当于挂起函数只能直接或间接地在协程中进行调用
- suspend关键字只是起一个标识作用,用以表明被suspend修饰的函数(也即挂起函数)内部存在耗时操作,因此必须放置在协程中进行调用。
- suspend关键字标识一个挂起点,挂起点具备挂起和恢复执行作用。当协程调用suspend函数时,会挂起当前协程,开启一个异步任务,当异步任务完成后,就会在当前挂起点恢复协程,继续该协程后续任务。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG,"start")
GlobalScope.launch {
val token = getToken()
Log.d(TAG,"token"+token)
val userInfo = getUserInfo(token)
Log.d(TAG,"userInfo"+userInfo)
setUserInfo(userInfo)
Log.d(TAG,"userInfofinish")
}
Log.d(TAG,"end")
repeat(8){
Log.e(TAG,"主线程执行$it")
}
Log.d(TAG,"finish")
}
private fun setUserInfo(userInfo: String) {
Log.e(TAG, userInfo)
}
private suspend fun getToken(): String {
delay(5000)
return "token"
}
private suspend fun getUserInfo(token: String): String {
delay(2000)
return "$token - userInfo"
}
2020-11-08 23:12:38.920 16679-16679/com.example.coroutinesdemo D/test: start
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo D/test: end
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行0
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行1
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行2
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行3
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行4
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行5
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行6
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo E/test: 主线程执行7
2020-11-08 23:12:38.924 16679-16679/com.example.coroutinesdemo D/test: finish
2020-11-08 23:12:43.928 16679-16717/com.example.coroutinesdemo D/test: tokentoken
2020-11-08 23:12:45.931 16679-16717/com.example.coroutinesdemo D/test: userInfotoken - userInfo
2020-11-08 23:12:45.931 16679-16717/com.example.coroutinesdemo E/test: token - userInfo
2020-11-08 23:12:45.931 16679-16717/com.example.coroutinesdemo D/test: userInfofinish
这里可以明显看出,suspend函数会将整个协程挂起(阻塞协程),而不仅仅是这个suspend函数,也就是说一个协程中有多个挂起函数时,它们是顺序执行的 上一个不执行完毕下一个不执行
2.async:Deferred
async跟launch的用法基本一样,区别在于:async的返回值是Deferred,将最后一个封装成了该对象。async可以支持并发,此时一般都跟await一起使用
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG,"start")
GlobalScope.launch {
val result1 = GlobalScope.async {
getResult1()
}
val result2 = GlobalScope.async {
getResult2()
}
val result = result1.await() + result2.await()
Log.e(TAG,"result = $result")
}
Log.d(TAG,"finish")
}
private suspend fun getResult1(): Int {
delay(3000)
return 1
}
private suspend fun getResult2(): Int {
delay(4000)
return 2
}
2020-11-09 09:08:08.923 26272-26272/com.example.coroutinesdemo D/test: start
2020-11-09 09:08:08.938 26272-26272/com.example.coroutinesdemo D/test: finish
2020-11-09 09:08:12.948 26272-26311/com.example.coroutinesdemo E/test: result = 3
async是不阻塞线程的,也就是说getResult1和getResult2是同时进行的,所以获取到result的时间是4s,而不是7s
当然我们还可以显示指定async返回值
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG,"start")
GlobalScope.launch {
val result1 = GlobalScope.async {
getResult1()
return@async "a"
}
val result2 = GlobalScope.async {
getResult2()
return@async "t"
}
val result = result1.await() + result2.await()
Log.e(TAG,"result = $result")
}
Log.d(TAG,"finish")
}
private suspend fun getResult1(): Int {
delay(3000)
return 1
}
private suspend fun getResult2(): Int {
delay(4000)
return 2
}
2020-11-09 09:31:05.404 31585-31585/com.example.coroutinesdemo D/test: start
2020-11-09 09:31:05.441 31585-31585/com.example.coroutinesdemo D/test: finish
2020-11-09 09:31:09.501 31585-31627/com.example.coroutinesdemo E/test: result = at
3.withContext
withContext 不创建新的协程,在指定协程上运行代码块
我们先来个有意思的测试,直接在我们的代码中声明withContext看看是否可以运行成功
可以看到直接爆红了 哈哈 来看一下错误提示
这里可以看到提示说挂起函数withContext只能被调用在coroutine或者另一个挂起函数中
所以说如果要使用withContext我们就必须将他放入coroutine或者挂起函数中
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "start")
GlobalScope.launch{
val time1 = System.currentTimeMillis()
val task1 = withContext(Dispatchers.IO) {
delay(2000)
Log.e(TAG, "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
"one" //返回结果赋值给task1
}
val task2 = withContext(Dispatchers.IO) {
delay(1000)
Log.e(TAG, "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
"two" //返回结果赋值给task2
}
Log.e(
TAG,
"task1 = $task1 , task2 = $task2 , 耗时 ${System.currentTimeMillis() - time1} ms [当前线程为:${Thread.currentThread().name}]"
)
}
Log.d(TAG, "finish")
}
2020-11-09 10:43:34.912 17381-17381/com.example.coroutinesdemo D/test: start
2020-11-09 10:43:34.926 17381-17381/com.example.coroutinesdemo D/test: finish
2020-11-09 10:43:36.936 17381-17419/com.example.coroutinesdemo E/test: 1.执行task1.... [当前线程为:DefaultDispatcher-worker-1]
2020-11-09 10:43:37.941 17381-17428/com.example.coroutinesdemo E/test: 2.执行task2.... [当前线程为:DefaultDispatcher-worker-5]
2020-11-09 10:43:37.947 17381-17420/com.example.coroutinesdemo E/test: task1 = one , task2 = two , 耗时 3019 ms [当前线程为:DefaultDispatcher-worker-2]
从上面结果可以看出,多个withConext是串行执行,如上代码执行顺序为先执行task1再执行task2,共耗时两个任务的所需时间的总和。这是因为withConext是个加粗样式 ,当运行到 withConext 时会将整个协程挂起(相当于阻塞整个协程),直到withConext执行完成后再执行下面的方法。所以withConext可以用在一个请求结果依赖另一个请求结果的这种情况。
如果同时处理多个耗时任务,且这几个任务都无相互依赖时,可以使用 async … await() 来处理,将上面的例子改为 async 来实现如下
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "start")
GlobalScope.launch{
val time1 = System.currentTimeMillis()
val task1 = async(Dispatchers.IO) {
delay(2000)
Log.e(TAG, "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
"one" //返回结果赋值给task1
}
val task2 = async(Dispatchers.IO) {
delay(1000)
Log.e(TAG, "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
"two" //返回结果赋值给task2
}
Log.e(TAG, "task1 = ${task1.await()} , task2 = ${task2.await()} , 耗时 ${System.currentTimeMillis() - time1} ms [当前线程为:${Thread.currentThread().name}]")
}
Log.d(TAG, "finish")
}
2020-11-09 10:52:04.749 17876-17876/com.example.coroutinesdemo D/test: start
2020-11-09 10:52:04.763 17876-17876/com.example.coroutinesdemo D/test: finish
2020-11-09 10:52:05.770 17876-17931/com.example.coroutinesdemo E/test: 2.执行task2.... [当前线程为:DefaultDispatcher-worker-1]
2020-11-09 10:52:06.771 17876-17931/com.example.coroutinesdemo E/test: 1.执行task1.... [当前线程为:DefaultDispatcher-worker-1]
2020-11-09 10:52:06.773 17876-17933/com.example.coroutinesdemo E/test: task1 = one , task2 = two , 耗时 2009 ms [当前线程为:DefaultDispatcher-worker-3]
4.runBlocking
launch和runBlocking区别是 runBlocking 里面的 delay 会阻塞线程,而 launch 创建的不会
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "start")
runBlocking {
// 阻塞1s
delay(1000)
Log.d(TAG, "this is a coroutine")
}
// 阻塞2s
Thread.sleep(2000)
Log.d(TAG, "main end")
Log.d(TAG, "finish")
}
2020-11-09 11:08:03.509 19555-19555/com.example.coroutinesdemo D/test: start
2020-11-09 11:08:04.524 19555-19555/com.example.coroutinesdemo D/test: this is a coroutine
2020-11-09 11:08:06.525 19555-19555/com.example.coroutinesdemo D/test: main end
2020-11-09 11:08:06.525 19555-19555/com.example.coroutinesdemo D/test: finish
ok 到这里协程的启动方式就算结束了
扩展
协程经常拿来与线程进行对比,它们彼此很相似,但是也很不同。
可以简单理解如下:
- 一个进程可以包含多个线程
- 一个线程可以包含多个协程
由于一个线程可以包含多个协程,而协程具备挂起和恢复功能,也因此让我们具备了在一个线程上执行多个异步任务的能力。
同步与异步是针对返回结果来说的,
对于同步调用,由调用者主动获取结果。
对于异步调用,调用者是通过回调等方式被动获取结果的。
同步异步的含义
-
同步:执行一个任务时,调用者调用后即可获取返回结果
-
异步:执行一个任务时,调用者调用后直接返回,不关心结果,而是等到任务结束时,通过回调等方式通知调用者结果
简单理解,比如对于一个函数调用,
同步调用就是调用函数后,直接就可以获取结果。
异步调用就是调用函数后,不关心结果,等函数体内的任务结束时,通过回调等方式通知调用者结果。
阻塞非阻塞的含义
-
阻塞:执行一个任务( 函数)时,当前调用线程调用后立即被挂起,无法执行后续代码
-
非阻塞:执行一个任务时( 函数),当前调用线程调用后立即返回,可继续执行后续代码
阻塞和非阻塞是针对当前线程是否具备 CPU 执行权来说的,
对于阻塞调用,调用不立即返回,当前线程被挂起,失去 CPU 执行权,直至调用任务完成,返回结果。
对于非阻塞调用,调用立即返回,当前线程仍然拥有 CPU 执行权,可继续执行后续代码。
ok ,文章到此暂告一段落 (楼上装修要吵死了 脑瓜子嗡嗡的)
本文转载
链接: Kotlin - 协程 简介
作者:Whyn