I have been studying coroutines recently. What is a coroutine. Beyond the scope of this article. We’ll talk about that later. Difference between coroutines and threads. This article mainly introduces the start mode of coroutine. Yes, you heard me right. Coroutines also have boot mode

The construction method of coroutines

Take a look at the coroutine constructor, whose second argument is the start mode. There is also a DEFAULT mode, DEFAULT

public fun CoroutineScope.launch(
    // Context callers
    context: CoroutineContext = EmptyCoroutineContext,
    // Start mode
    start: CoroutineStart = CoroutineStart.DEFAULT,
    // Coroutine method
    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
}
Copy the code

Startup mode Type

Click on CoroutineStart and the enumeration type of the startup mode is DEFAULT

public enum class CoroutineStart {
    // After the coroutine is created. Start dispatch immediately. If the coroutine is cancelled before scheduling. Enter the state of canceling the response directly
    DEFAULT,
    // We only execute when we need it, otherwise we don't.
    LAZY,
    // Start scheduling immediately, the coroutine will not respond to cancellation until the first point of suspension
    ATOMIC,
    // The coroutine is created and executed immediately in the call stack of the current function. Until the first hang point is encountered.
    UNDISPATCHED
}
Copy the code

To understand the startup mode, there are a few concepts to understand

  • Scheduling does not mean execution.

The scheduling here is actually talking about the first parameter. Context CoroutineContext. For the moment, you can think of it as a queue to which coroutine tasks are added. It doesn’t mean it’s executed.

  • There is time between scheduling and execution
  • Coroutines can be cancelled

DEFAULT

Check the DEFAULT. Website introduction

Once the coroutine is created. Start dispatch immediately. If the coroutine is cancelled before scheduling. Enter the state of canceling the response directly

Let’s start with a picture drawn by the author. I’ll explain it later

Once the coroutine is created. Immediately start dispatching the understanding of this sentence. It’s interesting. Coroutine tasks are added to the scheduler, also known as queues. The start() or join() task triggers itself without having to go there. Here’s the test code. Whether or not to start() has no effect on the test results.

If the coroutine is cancelled before scheduling. Go straight to the cancel response state, which is even more interesting. Start by creating a coroutine task and adding it to the queue. The task will trigger itself. The timing of cancel is difficult to determine, whether before or after the task is executed, even after the coroutine is created. Cancel immediately, or maybe the scheduler is already executing a block of coroutine code.

In explaining the following test code, the author in order to obtain multiple results. After 100 runs, delay(100) is a suspend function, which will be explained later. Sleep (20) is on the thread executing the method. Block for 20 milliseconds. The purpose is to ensure that tasks are added to the scheduler.

private fun todoModuleDefault(
      start: CoroutineStart = CoroutineStart.DEFAULT,
      isStart: Boolean = false
  ) {
      for (i in 1.100.) {
          val buffer = StringBuffer()
          buffer.append("start")
          val job = GlobalScope.launch(start = start) {
              buffer.append("-> Execute coroutine")
              delay(100)
              buffer.append("-> Coroutine execution completed")
          }
          buffer.append("-> Start coroutine")
          // Whether to start does not affect the result
          if (isStart) {
              job.start()
          }
          // Make sure start() is executed
          sleep(20)
          buffer.append("-> Remove coroutine")
          job.cancel()
          buffer.append("->end")
          println("The first${i}time$buffer")}}Copy the code

The result is as follows. The order in which coroutines are started and executed changes. As we said above. The coroutine task is added after the scheduler. It will be executed immediately. It doesn’t matter if you have start() or not and who comes first depends on the state of the scheduler at the time. Out of control.

The cancel() method is executed after 20 milliseconds. The coroutine is already suspended. Delay (100). Cancel () at this point does not complete the print coroutine execution. It also proves that coroutines can be cancelled.

The first13Start -> start-> execute coroutine -> remove coroutine ->end17Start -> Execute coroutine -> Start coroutine -> remove coroutine ->endCopy the code

LAZY

LAZY

We execute when we need him to execute, or we don’t execute. If it’s canceled before it’s scheduled. Then go straight to the abnormal end state

Start () or join() will not start until it is executed. Change the above test code to LAZY mode. The isStart variable is changed to true and the result is as follows

Start -> Start -> execute coroutine -> remove coroutine ->endCopy the code

The test results for false without start() are as follows

Start -> Start -> remove ->endCopy the code

Obviously. Whether to perform coroutines. It all depends on whether I start()

If it’s canceled before it’s scheduled. Then you go straight to the exception end state, which means you add the coroutine task to the scheduler. Waiting to be executed. At this point it will cancel. Then the coroutine is not executing.

🌰 for example

Take a look at the following example. Start () after intentionally cancel()

private fun todoModuleLazyCancel(
    start: CoroutineStart = CoroutineStart.LAZY
) {
    for (i in 1.100.) {
        val buffer = StringBuffer()
        buffer.append("start")
        val job = GlobalScope.launch(start = start) {
            buffer.append("-> Execute coroutine")
            delay(100)
            buffer.append("-> Coroutine execution completed")
        }
        buffer.append("-> Remove coroutine")
        job.cancel()
        // Make sure cancel() is executed
        sleep(20)
        buffer.append("-> Start coroutine")
        job.start()
        buffer.append("->end")
        println("The first${i}time$buffer")}}Copy the code

As a result, the coroutine will no longer be executed.

Start -> Remove coroutine -> Start coroutine ->endCopy the code

ATOMIC

ATOMIC

Scheduling starts immediately, and cancellations are not responded to until the coroutine reaches the first hang point

A suspend point is the point at which the suspend function, the method function decorated by suspend, is executed. In the example, delay is a suspend function

He’s kind of like DEFAULT. They all start dispatching directly. Don’t go to start() or join().

The difference between DEFAULT and DEFAULT is that it does not respond to cancellation until the first suspension point is reached. DEFAULT does not require cancellation at any time.

🌰 for example

If you change the schema to ATOMIC, whether or not you start() will not affect the result

private fun todoModuleAtomic(
    start: CoroutineStart = CoroutineStart.ATOMIC,
    isStart: Boolean = false
) {
    for (i in 1.100.) {
        val buffer = StringBuffer()
        buffer.append("start")
        val job = GlobalScope.launch(start = start) {
            buffer.append("-> Execute coroutine")
            delay(100)
            buffer.append("-> Coroutine execution completed")
        }
        buffer.append("-> Start coroutine")
        // Whether to start does not affect the result
        if (isStart) {
            job.start()
        }
        // Make sure start() is executed
        sleep(20)
        buffer.append("-> Remove coroutine")
        job.cancel()
        buffer.append("->end")
        println("The first${i}time$buffer")}}Copy the code

The result is as follows: This is decided because cancel() is not responded to before the first hang point. Coroutines must be executed

2021-01-19 20:50:58.780I: the first3Start -> Start -> execute coroutine -> remove coroutine ->end2021-01-19 20:50:58.982I: the first4Start -> Execute coroutine -> Start coroutine -> remove coroutine ->endCopy the code

UNDISPATCHED

The coroutine is created and executed immediately in the call stack of the current function. Until the first hang point is encountered.

At first glance it looks very similar to ATOMIC. Take note. This is on the call stack of the current function. What does that mean? The thread in which you execute the coroutine. Take the following example. I did it on the main thread. So before the first hanging point. That’s before delay(20). Also on the main thread. The ATOMIC is the thread created in the scheduler.

private fun todoModuleUnDispatched(
    start: CoroutineStart = CoroutineStart.UNDISPATCHED,
    isStart: Boolean = false
) {
    for (i in 1.100.) {
        val buffer = StringBuffer()
        buffer.append("start")
        val job = GlobalScope.launch(start = start) {
            buffer.append("-> Execute coroutine A")
            buffer.append("[${Thread.currentThread().name}]. "")
            delay(20)
            buffer.append("-> Execute coroutine B")
            buffer.append("[${Thread.currentThread().name}]. "")
            delay(100)
            buffer.append("-> Coroutine execution completed")
        }
        buffer.append("-> Start coroutine")
        // Whether to start does not affect the result
        if (isStart) {
            job.start()
        }
        // Make sure start() is executed
        sleep(50)
        buffer.append("-> Remove coroutine")
        job.cancel()
        buffer.append("->end")
        println("The first${i}time$buffer")}}Copy the code

Logs in UNDISPATCHED mode. In the main thread

Start -> Execute coroutine A[main]-> Start coroutine -> execute coroutine B[DefaultDispatcher-worker-1]-> delete ->endCopy the code

Log in LAZY mode, in background thread.

Start -> Start coroutine -> execute coroutine A[DefaultDispatcher-worker-2]-> Execute the coroutine B[DefaultDispatcher-worker-1[DefaultDispatcher-worker-]-> end start-> start-> execute coroutine A[DefaultDispatcher-worker-1]-> Execute the coroutine B[DefaultDispatcher-worker-1]-> delete ->endCopy the code

One more thing. Because UNDISPATCHED, the coroutine is created immediately in the call stack of the current function. So his coroutine must also be executed. So ah. In the four modes, LAZY and UNDISPATCHED are the two total start modes that have coroutines executed.

The last

Coroutines are an interesting thing. The author here may not understand comprehensively. There is an error. Please correct me. The wrong article will mislead more people.

I said earlier that the scheduler is a queue. It’s not accurate. Just to make it clear what each mode of activation is. I came up with an alternative. Do not directly task scheduler CoroutineContext is the queue.

Ok, so that’s the startup mode for coroutines.

reference

Cracking Kotlin coroutines (2) – Coroutines starter