Kotlin(二十)协程(上下文和调度器)
1.调度器和线程
协程调度器Dispatchers 可以将协程运行在指定的线程上,也可以将其分派到线程池中,或者让她无限制的运行
package com.example.kotlin01
import kotlinx.coroutines.*
fun main() {
runBlocking {
launch {
println("main runBlocking in ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("Dispatchers.Default in ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Dispatchers.Unconfined in ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("NewThread")) {
println("newSingleThreadContext in ${Thread.currentThread().name}")
}
}
}
package com.example.kotlin01
import kotlinx.coroutines.*
fun main() {
runBlocking {
launch {
println("main runBlocking in ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("Dispatchers.Default in ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Dispatchers.Unconfined in ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("NewThread")) {
println("newSingleThreadContext in ${Thread.currentThread().name}")
}
}
}
Dispatchers.Unconfined in main
Dispatchers.Default in DefaultDispatcher-worker-1
newSingleThreadContext in NewThread
main runBlocking in main
以上四个协程分别指定运行在指定的线程中。分别打印出了其所在线程的名字。不带参数和Unconfined的协程,运行在main主线程中。而Default则运行在 DefaultDispatcher-worker-1线程。newSingleThreadContext参数,我们指定了一个新的线程NewThread。由于我们手动创建了新的线程。线程的资源非常稀缺,我们在不用的时候可以close掉。或者存在应用的顶级变量重复使用。
2.Unconfined 调度器 vs confined 调度器
调度器默认继承外部的协程作用域,比如runBlocking 启动的协程的调度器只能说调用者所在线程。Unconfined 调度器在调用者线程启动一个协程,仅仅运行倒第一个挂起点,挂起之后将恢复线程中的协程。
package com.example.kotlin01
import kotlinx.coroutines.*
fun main() {
runBlocking {
launch {
println("main runBlocking in ${Thread.currentThread().name}")
delay(1200)
println("main after runBlocking in ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Dispatchers.Unconfined in ${Thread.currentThread().name}")
delay(1200) //恢复线程中的协程
println("Dispatchers after.Unconfined in ${Thread.currentThread().name}")
}
}
}
Dispatchers.Unconfined in main
main runBlocking in main
Dispatchers after.Unconfined in kotlinx.coroutines.DefaultExecutor
main after runBlocking in main
runBlocking 默认继承了上下文 的协程在主线程中(main)执行,而调度器是Unconfined的协程,在delay函数之后代码默认运行于delay函数所在的运行线程。kotlinx.coroutines.DefaultExecutor。而confined调度器则并非如此,confined调度器的协程默认一直运行在runBlocking继承了上下文的线程(Main)
3. 调试协程和线程(Debugging coroutines and threads)
协程可以在一个线程中挂起,在另外一个线程继续执行,这样以来很难知道协程当前在做什么,再那个线程中执行,处于什么状态。通常再一些日志框架中,会打印出当前协程所在线程
package com.example.kotlin01
import kotlinx.coroutines.*
fun log(msg:String) = println("${Thread.currentThread().name}--"+msg)
fun main() {
runBlocking {
async {
log("aaa")
}
async {
log("bb")
}
async {
log("cc")
}
}
}
main--aaa
main--bb
main--cc
4. 在线程间切换(Jumping between threads)
在一个协程中切换运行于不同的线程。
package com.example.kotlin01
import kotlinx.coroutines.*
fun log(msg: String) = println("${Thread.currentThread().name}--" + msg)
fun main() {
newSingleThreadContext("Ctx1").use { ctx1 ->
newSingleThreadContext("Ctx2").use { ctx2 ->
//在Ctx1线程执行协程
runBlocking(ctx1) {
log("this is ctx1")
//切换线程到Ctx2继续执行协程,依然保持在另外一个协程中
withContext(ctx2) {
log("this is ctx2")
}
}
}
}
}
Ctx1--this is ctx1
Ctx2--this is ctx2
通过newSingleThreadContext指定两个线程Ctx1,Ctx2。标准库中的 use 函数用来不需要时候释放newSingleThreadContext 所创建的线程。runBlocking构建作用区域,运行协程,
第一个打印,在ctx1线程中执行,通过withContext函数,更改协程的上下文,进行切换线程。切换到了ctx2线程。继续打印,因此当前协程运行在了ctx2线程。
5. 上下文中的 Job(Job in the context)
协程中的Job是其上下文的一部分,可以通过coroutineContext[Job]获取。
fun main() {
runBlocking {
launch {
println("My Job is ${coroutineContext[Job]}")
}
}
}
My Job is StandaloneCoroutine{Active}@1ed6993a
6. 子协程(Children of a coroutine)
当一个协程在另外一个协程中启动的时候,默认继承了上面的协程的上下文,上面的协程称之为父协程。
父协程取消执行的时候,子线程也会跟着取消。但是如果子线程用的作用区域是GlobalScope,全局作用区域,则不会影响
fun main() {
runBlocking {
var job = launch {
println("这是父协程")
GlobalScope.launch {
println("这是第二个子协程---")
delay(1000)
println("这是第二个子协程***")
}
launch {
println("这是第一个子协程---")
delay(1000)
println("这是第一个子协程***")
}
}
delay(500)
job.cancelAndJoin()
//保证jvm线程活跃
delay(1500)
}
这是父协程
这是第二个子协程---
这是第一个子协程---
这是第二个子协程***
7. 父协程的职责(Parental responsibilities)
父协程会等待所有子协程完成任务
fun main() {
runBlocking {
var job = launch {
launch {
repeat(5){
i->
println("这是第{$i}个子协程---")
}
}
println("这是父协程")
}
}
}
这是父协程
这是第{0}个子协程---
这是第{1}个子协程---
这是第{2}个子协程---
这是第{3}个子协程---
这是第{4}个子协程---
8. 为协程命名以便调试(Naming coroutines for debugging)
使用CoroutineName为协程命名
package com.example.kotlin01
import kotlinx.coroutines.*
fun log(msg: String) = println("[${Thread.currentThread().name}]" + msg)
fun main() {
runBlocking(CoroutineName("main")) {
val result = async(CoroutineName("这是我的别名")) {
log("你好")
5
}
println(result.await())
}
}
9. 组合上下文元素(Combining context elements)
有时候我们需要为协程上下文定义多个元素,我们可以使用+运算符,比如我们可以同时使用显示指定的调度器和显示的指定名称来启动协程
package com.example.kotlin01
import kotlinx.coroutines.*
fun log(msg: String) = println("[${Thread.currentThread().name}]" + msg)
fun main() {
runBlocking(CoroutineName("main")) {
val result = async(Dispatchers.Default+CoroutineName("这是我的别名")) {
log("你好")
5
}
println(result.await())
}
}
[DefaultDispatcher-worker-1]你好
5
10. 协程作用域(Coroutine scope)
假如我们在一个Activity中启动了N个协程执行任务,并在Actvity销毁的时候,结束掉协程。这个时候我们能想到通过Actvity的声明周期和协程绑定显示的cancel协程,我们也可以通过实现CoroutineScope 接口。下面具体实现方案
package com.example.kotlin01
import kotlinx.coroutines.*
class MyActivity:CoroutineScope by CoroutineScope(Dispatchers.Default) {
fun onDestory(){
cancel()
}
fun doSomthing(){
repeat(10){ i->
launch {
delay((i + 1) * 200L) // var
println("这是第${i}次执行...")
}
}
}
}
suspend fun main() {
val myActivity = MyActivity()
myActivity.doSomthing()
delay(500)
myActivity.onDestory()
delay(2000)
}
这是第0次执行...
这是第1次执行...
打印了两次之后,结束掉了Actvity,后续协程没有继续执行。
11. 线程局部数据(Thread-local data)
有时候我们需要将线程的一些局部数据传递到协程中,但是协程没有绑定任何线程,所以我们可以通过ThreadLocal来实现,ThreadLocal的扩展函数asContextElement 可以解决这个问题
val threadLocal = ThreadLocal<String>()
fun main() {
threadLocal.set("this is new data")
runBlocking {
val job = launch(Dispatchers.Default+ threadLocal.asContextElement(value = "launch")) {
println("start data is ${threadLocal.get()}")
yield()
println("stop data is ${threadLocal.get()}")
}
job.join()
println("and data is ${threadLocal.get()}")
}
}
start data is launch
stop data is launch
and data is this is new data