This article is based on the official documentation of coroutines, which can be found here

The coroutine context contains schedulers, asynchronous processors, interceptors, and so on. Interceptors, of course, are not usually used; they are mainly used for thread switching. The first argument to launch(XXX){} or async(XXX){} is our coroutine context.

First, 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
}
Copy the code

There are four default schedulers, which we can choose according to our own business.

1.1, Dispatchers. Unconfined

fun printMsg(msg:String){
   println("${Thread.currentThread().name}  "+msg)
}
fun main(a) = runBlocking {                  / / main thread
    val job = launch(Dispatchers.Unconfined) {
        printMsg("unconfined before") #1     / / main thread
        delay(500L)                   #2     // background threads
        printMsg("unconfined after")  #3     // background threads
    }
    job.join()
    printMsg("Done")}Copy the code

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Delay must be executed by the background thread, and the thread of #3 after delay is determined when delay is restored, and its thread is the thread of delay execution.

1.2. Customize the scheduler (remember close)

fun main(a) = 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")}Copy the code

Here we create a scheduler with newSingleThreadContext, and thenA good habit is that it uses the use of close.

2. Get jobs

The job can be returned using the launch{} function or retrieved using coroutineContext[job].

fun main(a) = 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("Current subcoroutine Job$job")
            job.join()
        }
    }
    printMsg("Done")}Copy the code

WithContext doesn’t have a new coroutine, it just switches the scheduler.

3. Parent-child coroutine relationship

3.1. The default parent coroutine is cancelled, and the child coroutine is also cancelled.

fun main(a) = 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")}Copy the code

The parent coroutine is canceled, the child coroutine is canceled. We can catch the exception on cancellation.

fun main(a) = 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")}Copy the code

The parent coroutine cancels and the child coroutine catches the cancellation exception.

3.2 Scenario where the parent coroutine cancels and the child coroutine does not cancel

3.2.1 GlobalScope.launch Launches a separate coroutine

Globalscope. launch{XXX}, which is not strictly a child of an external coroutine, opens a separate coroutine with a lifetime independent of the outer coroutine.

fun main(a) = runBlocking {
    var job1: Job? = null
    var request = launch(Dispatchers.Default) {
        job1 = GlobalScope.launch {  // start a separate coroutine
            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")}Copy the code

Apparently job1 did its job without responding to the cancellation of the external coroutine.

3.2.2 Adding a Job Object

fun main(a) = 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")}Copy the code

4. Custom CoroutineName CoroutineName

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

        }
        launch(CoroutineName("V22 coroutines." ")) {
            delay(100)
            printMsg("job2: before")
            delay(1000)
            printMsg("job2: after")
        }
    }
    request.join()
    printMsg("who can survived")}Copy the code

Add + to add context

fun main(a) = runBlocking {
    launch(CoroutineName("V1coroutine") + Dispatchers.Default) {   // Add context directly
        printMsg("job1:I run in my own job and execute independently")
        delay(3000)
        printMsg("job1:I am not affected by cancellation ")
    }.join()
    printMsg("Done")}Copy the code

Six, android MainScope use

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

Seven, Thread_local Data

val threadLocal = ThreadLocal<String>()
fun main(a) = 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()}")}Copy the code