协程系列(五)Coroutine context and dispatchers

1,249 阅读1分钟

本文基于协程官方文档讲解,具体可查看here

协程上下文包含调度器、异步处理器、拦截器等。当然拦截器一般我们不会用到,它主要是用来进行线程切换的。 launch(xxx){}或者async(xxx){}的第一个参数就是我们的协程上下文。

一、调度器 Dispatchers

public actual object Dispatchers {
    public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
    public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

默认四种调度器,我们可以依据自己的业务选择。

1.1、Dispatchers.Unconfined

fun printMsg(msg:String){
   println("${Thread.currentThread().name}  "+msg)
}
fun main() = runBlocking {                  //main线程
    val job = launch(Dispatchers.Unconfined) {
        printMsg("unconfined before") #1     //main线程
        delay(500L)                   #2     //后台线程
        printMsg("unconfined after")  #3     //后台线程
    }
    job.join()
    printMsg("Done")
}

image.png 子协程体里面的代码执行,可以看成三部分,delay前#1、delay#2、delay后#3,Dispatchers.Unconfined调度器,在子协程启动时,调度的线程是当前线程,这里就是main线程。delay肯定是后台线程执行,delay后#3所在的线程是delay执行完恢复时确定,其线程是delay执行所在的线程。

1.2、自定义调度器(需记得close)

fun main() = runBlocking {
    newSingleThreadContext("Ctx1").use { ctx1 ->
        newSingleThreadContext("Ctx2").use { ctx2 ->
            val job = launch(ctx1) {
                printMsg("start in ctx1")
                withContext(ctx2) {
                    printMsg("work in ctx2")
                }
                printMsg("Back to ctx1")
            }
            job.join()
        }
    }
    printMsg("Done")
}

image.png 这里通过newSingleThreadContext创建了一个调度器,然后习惯很好的点是它用了use,用完就close了

二、获取Job

可以通过launch{}函数返回job,也可以使用coroutineContext[Job]来获取。

fun main() = runBlocking {
    newSingleThreadContext("Ctx1").use { ctx1 ->
        newSingleThreadContext("Ctx2").use { ctx2 ->
            val job = launch(ctx1) {
                printMsg("start in ctx1   ${coroutineContext[Job]}")
                withContext(ctx2) {
                    printMsg("work in ctx2    ${coroutineContext[Job]}")
                }
                printMsg("Back to ctx1    ${coroutineContext[Job]}")
            }
            printMsg("当前子协程job     $job")
            job.join()
        }
    }
    printMsg("Done")
}

image.png withContext没有新开协程,只是切换了调度器而已。

三、父子协程关系

3.1、默认父协程取消,子协程也会取消。

fun main() = runBlocking {
    var request = launch(Dispatchers.Default){
        launch {
            printMsg("job1:before")
            delay(2000)
            printMsg("job1:after")
        }
        launch {
            delay(100)
            printMsg("job2 before")
            delay(1000)
            printMsg("job2: after")
        }
    }
    delay(500)
    request.cancel()
    delay(1000)
    printMsg("who can survived")
}

image.png 父协程取消了,子协程都响应取消了。我们可以捕捉下取消时的异常。

fun main() = runBlocking {
    var request = launch(Dispatchers.Default) {
        launch {
            try {
                printMsg("job1:before")
                delay(2000)
                printMsg("job1:after")
            } catch (e: Exception) {
                printMsg("job1    $e")
            }
        }
        launch {
            try {
                delay(100)
                printMsg("job2: before")
                delay(1000)
                printMsg("job2: after")
            } catch (e: Exception) {
                printMsg("job2    $e")
            }
        }
    }
    delay(500)
    request.cancel()
    delay(1000)
    printMsg("who can survived")
}

image.png 父协程取消,子协程都能捕捉到这个取消异常。

3.2、父协程取消,子协程不取消的场景

3.2.1 GlobalScope.launch开启一个独立的协程

GlobalScope.launch{xxx} ,严格来讲它不是外部协程的子协程,它开辟了一个独立的协程,其生命周期也跟外层协程无关。

fun main() = runBlocking {
    var job1: Job? = null
    var request = launch(Dispatchers.Default) {
        job1 = GlobalScope.launch {  //开启一个独立的协程了
            try {
                printMsg("job1:before")
                delay(2000)
                printMsg("job1:after")
            } catch (e: Exception) {
                printMsg("job1    $e")
            }
        }
        launch {
            try {
                delay(100)
                printMsg("job2: before")
                delay(1000)
                printMsg("job2: after")
            } catch (e: Exception) {
                printMsg("job2    $e")
            }
        }
    }
    delay(500)
    request.cancel()
    job1?.join()
    delay(1000)
    printMsg("who can survived")
}

image.png 很明显没有响应外部协程的取消,job1还是完成了自己的任务。

3.2.2 添加新的Job对象

fun main() = runBlocking {
    var job1: Job? = null
    var job2: Job? = null
    var request = launch(Dispatchers.Default) {
        job1 = launch(Job()) {                   //这里添加了Job()
            try {
                printMsg("job1:before")
                delay(2000)
                printMsg("job1:after")
            } catch (e: Exception) {
                printMsg("job1    $e")
            }
        }
        launch {
            try {
                delay(100)
                printMsg("job2: before")
                delay(1000)
                printMsg("job2: after")
            } catch (e: Exception) {
                printMsg("job2    $e")
            }
        }
    }
    delay(500)
    request.cancel()
    job1?.join()
    delay(1000)
    printMsg("who can survived")
}

image.png

四、自定义协程名CoroutineName

fun main() = runBlocking {
    var request = launch(Dispatchers.Default) {
         launch(CoroutineName("V11协程")) {
            printMsg("job1:before")
            delay(2000)
            printMsg("job1:after")

        }
        launch(CoroutineName("V22协程")) {
            delay(100)
            printMsg("job2: before")
            delay(1000)
            printMsg("job2: after")
        }
    }
    request.join()
    printMsg("who can survived")
}

image.png

五、合并上下文 直接相加+

fun main() = runBlocking {
    launch(CoroutineName("V1coroutine") + Dispatchers.Default) {   //上下文直接相加
        printMsg("job1:I run in my own job and execute independently")
        delay(3000)
        printMsg("job1:I am not affected by cancellation ")
    }.join()
    printMsg("Done")
}

image.png

六、android的MainScope使用

class MainActivity : AppCompatActivity() {
    private var mainScope = MainScope()
    override fun onDestroy() {
        super.onDestroy()
        mainScope.cancel()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mainScope.launch { 
            delay(1000)
        }
    }
  }

七、Thread_local Data

val threadLocal = ThreadLocal<String>()
fun main() = runBlocking {
    threadLocal.set("aaaaa")
    launch(CoroutineName("V1coroutine") + Dispatchers.Default +
            threadLocal.asContextElement(value = "bbbbb")) {
        printMsg("V1coroutine thread ${threadLocal.get()}")
        delay(3000)
        printMsg("V1coroutine thread ${threadLocal.get()}")
    }
    delay(500)
    printMsg("current thread   ${threadLocal.get()}")
}

image.png