简介
介绍
此篇文章主要介绍了kotlin上下文与调取器,如果之前没有接触过协程,可以参考下面的文章目录
参考文档
文章目录
协程调度器
公共日志方法
代码如下:
private fun log(msg:String){
Log.d(Constants.TAG,"[${Thread.currentThread().name}]$msg")
}
简单调度器示例
代码如下:
private fun test() = runBlocking {
launch {
log("launch-null")
}
launch(Dispatchers.Unconfined) {
log("launch-Unconfined")
}
launch(Dispatchers.IO) {
log("launch-IO")
}
launch(Dispatchers.Default) {
log("launch-Default")
}
}
日志如下:
2021-09-26 14:26:34.110 31096-31096/demo.demo.democoroutines D/Coroutines: [main]launch-Unconfined
2021-09-26 14:26:34.136 31096-31134/demo.demo.democoroutines D/Coroutines: [DefaultDispatcher-worker-3]launch-Default
2021-09-26 14:26:34.136 31096-31132/demo.demo.democoroutines D/Coroutines: [DefaultDispatcher-worker-1]launch-IO
2021-09-26 14:26:34.136 31096-31096/demo.demo.democoroutines D/Coroutines: [main]launch-null
日志分析:
- 我们来看运行线程,可以看到Unconfined调度的时候运行在当前线程和不传的时候都是运行在当前线程"main",而Default和IO的调度的时候运行在线程池中。
各个调度器介绍
- 不传参
不传参的时候协程运行在当前的线程 - Dispatchers.Unconfined
在第一个挂起点之前运行在启动该协程的线程,但是在挂起点之后则运行在那个线程由在哪个线程恢复决定,我们由下面例子验证这个问题
代码如下:
launch(Dispatchers.Unconfined) {
log("launch-Unconfined")
delay(500)
log("launch-Unconfined-delay")
}
日志如下:
2021-09-26 16:02:40.037 10145-10145/demo.demo.democoroutines D/Coroutines: [main]launch-Unconfined
2021-09-26 16:02:40.541 10145-10195/demo.demo.democoroutines D/Coroutines: [kotlinx.coroutines.DefaultExecutor]launch-Unconfined-delay
日志分析:
可以看出在delay挂起之前是运行在main线程的,但是 在delay之后已经不再main线程了
- Dispatchers.Default和Dispatchers.IO
这两个比较类似,都是运行在线程池中,从上面的简单示例中也能看出来 - Dispatchers.Main
这个是运行在UI线程中,目前看到的是现象是在子协程中不执行,在顶级协程中才执行,也就是说使用的时候要像如下代码来使用,我们用如下代码来验证其运行的线程
代码如下
private fun test() = runBlocking(Dispatchers.IO) {
GlobalScope.launch(Dispatchers.Main) {
log("launch-Main")
}
}
日志如下:
2021-09-26 16:18:28.824 12033-12033/demo.demo.democoroutines D/Coroutines: [main]launch-Main
日志分析
日志可以看出,虽然外部协程运行使用IO调度器,理应运行在线程池中,但是内部使用了Main调度器仍然运行在main线程。
但是注意在使用的时候要加上GlobalScope来调用,不能直接使用launcher。
launcher传其他参数
1. newSingleThreadContext
2.newFixedThreadPoolContext
代码如下:
launch(newSingleThreadContext("newSingleThreadContext")) {
log("launch-newSingleThreadContext")
}
launch(newFixedThreadPoolContext(3,"newFixedThreadPoolContext")) {
log("launch-newFixedThreadPoolContext")
}
日志如下:
2021-09-26 16:37:45.423 12933-13011/demo.demo.democoroutines D/Coroutines: [newSingleThreadContext]launch-newSingleThreadContext
2021-09-26 16:37:45.432 12933-13012/demo.demo.democoroutines D/Coroutines: [newFixedThreadPoolContext-1]launch-newFixedThreadPoolContext
日志分析:
- newSingleThreadContext是启动了新线程,名字就是里面的name参数。
- newFixedThreadPoolContext是启动了一个固定线程数的线程池,线程个数为第一个参数,线程池名称为第二个参数。
组合上下文中的元素
- 我们可以使用 + 操作符来实现。 比如说,我们可以显式指定一个调度器来启动协程并且同时显式指定一个命名,使用如下代码可以实现
代码如下:
launch(Dispatchers.Default + CoroutineName("test")) {
}
父协程和子协程
代码如下:
val task = launch {
launch {
delay(500)
log("launch子协程-delay(500)")
}
launch {
delay(2000)
log("launch子协程-delay(2000)")
}
GlobalScope.launch {
delay(2000)
log("GlobalScope协程")
}
}
delay(1000)
task.cancel()
log("task协程取消")
delay(3000)
val task1 = launch {
repeat(5) {
launch {
delay(200)
log("task1子协程任务$it")
}
}
}
task1.join()
log("task1父协程执行完成")
日志如下:
2021-09-26 17:24:18.077 14644-14644/demo.demo.democoroutines D/Coroutines: [main]launch子协程-delay(500)
2021-09-26 17:24:18.551 14644-14644/demo.demo.democoroutines D/Coroutines: [main]task协程取消
2021-09-26 17:24:19.613 14644-14699/demo.demo.democoroutines D/Coroutines: [DefaultDispatcher-worker-1]GlobalScope协程
2021-09-26 17:24:22.882 14644-14644/demo.demo.democoroutines D/Coroutines: [main]task1子协程任务0
2021-09-26 17:24:22.884 14644-14644/demo.demo.democoroutines D/Coroutines: [main]task1子协程任务1
2021-09-26 17:24:22.885 14644-14644/demo.demo.democoroutines D/Coroutines: [main]task1子协程任务2
2021-09-26 17:24:22.886 14644-14644/demo.demo.democoroutines D/Coroutines: [main]task1子协程任务3
2021-09-26 17:24:22.887 14644-14644/demo.demo.democoroutines D/Coroutines: [main]task1子协程任务4
2021-09-26 17:24:22.889 14644-14644/demo.demo.democoroutines D/Coroutines: [main]task1父协程执行完成
日志分析:
- 父协程取消则子协程取消(可以看出子协程的delay(500)打出来了,取消之后delay(2000)没有再打出来)
- GlobalScope启动的协程独立运行,没有父协程(GlobalScope启动的delay(2000)则不受外部协程取消的影响,正常打出来了)
- 父协程会等待所有子协程的结束(可以看出task1等待内部任务结束之后才打出来task1完成了)
协程的作用域
协程的作用域主要是协程是运行在一定的范围内的,这样当一个协程取消的时候里面的子协程也会取消。一个典型的场景:在Android进入一个Activity的时候我们进行一些的操作如网络请求或者一些耗时任务等,但是当Activity销毁的时候就需要将这些任务全部取消,我们通过delay来模拟耗时任务
代码如下:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
repeat(10){
mMainScope.launch {
delay(it*1000L)
log("协程执行任务->$it")
}
}
mMainScope.launch {
delay(2000)
finish()
}
}
private val mMainScope = MainScope()
override fun onDestroy() {
super.onDestroy()
mMainScope.cancel()
log("结束当前Activity")
}
private fun log(msg: String) {
Log.d(Constants.TAG, "[${Thread.currentThread().name}]$msg")
}
日志如下:
2021-09-27 13:45:02.719 9528-9528/demo.demo.democoroutines D/Coroutines: [main]协程执行任务->0
2021-09-27 13:45:03.723 9528-9528/demo.demo.democoroutines D/Coroutines: [main]协程执行任务->1
2021-09-27 13:45:04.725 9528-9528/demo.demo.democoroutines D/Coroutines: [main]协程执行任务->2
2021-09-27 13:45:05.383 9528-9528/demo.demo.democoroutines D/Coroutines: [main]结束当前Activity
日志分析:
- 代码通过循环启动来10个协程,分别进行不同时间的delay操作,分别为0,1000,2000等
- 从日志可以看出在destory取消,即打印出结束当前activity之后已经其余的时间更长的协程已经不能再打印日志了,说明任务已经被取消了。
- 除了MainScope之外,CoroutineScope(Dispatchers.IO)等也可以进行作用域管理,只是运行的线程不在main线程。
异步转同步操作
代码如下:
private fun test() = runBlocking {
val result = getResult()
log("result->$result")
}
private suspend fun getResult():Boolean{
return suspendCoroutine<Boolean> {
GlobalScope.launch {
delay(5000)
it.resume(true)
}
}
}
private fun log(msg: String) {
Log.d(Constants.TAG, "[${Thread.currentThread().name}]$msg")
}
日志如下:
2021-09-27 15:48:27.566 15625-15625/demo.demo.democoroutines D/Coroutines: [main]result->true
日志分析:
- 首先用启动协程延时5s模拟耗时操作。
- suspendCoroutine启动挂起协程,在耗时结束之后唤醒协程(resume)。
- 直接调用getResult方法,可以看到在延时5s之后打印出日志,但是写法却是同步直接调用的写法。
总结
本问主要介绍了协程的上下文与调度器、作用域、异步转同步等当然这也是本人的学习笔记,
希望也能对大家有那么一点点的帮助或者启发,我就很开心了。当然了本人也是在学习与理解的
过程中记录与理解难免有一定的认知局限性,如果发现有什么问题,欢迎指正,谢谢。