The development environment

  • IntelliJ IDEA 2021.2.2 (Community Edition)
  • Kotlin: 212-1.5.10 – release – IJ5284.40

We learned to start coroutines in our first example, but here are some of the basics.

Blocking and non-blocking

runBlocking

Delay is non-blocking, and Thread.sleep is blocking. Block explicitly using the runBlocking coroutine builder.

import kotlinx.coroutines.*

fun main(a) {
    GlobalScope.launch { // Start a new coroutine in the background and continue
        delay(200)
        "rustfisher.com".forEach {
            print(it)
            delay(280)
        }
    }
    println("The code in the main thread will execute immediately.")
    runBlocking {     // This expression blocks the main thread
        delay(3000L)  // block the main thread to prevent exiting too quickly
    }
    println("\n End of example")}Copy the code

As you can see, runBlocking uses delay to delay access. The main thread using runBlocking will block until the coroutine inside runBlocking completes. RunBlocking {delay} does that.

We can also wrap the main function with runBlocking.

import kotlinx.coroutines.*

fun main(a) = runBlocking {
    delay(100) // We can use delay here

    GlobalScope.launch {
        delay(100)
        println("Fisher")
    }
    print("Rust ")
    delay(3000)}Copy the code

The

in runBlocking

can be omitted for now.

RunBlocking is also available in testing

/ / introduce junit
dependencies {
    implementation("Junit: junit: 4.13.1")}Copy the code

Unit testing

Use @test to set up tests

import org.junit.Test
import kotlinx.coroutines.*

class C3Test {

    @Test
    fun test1(a) = runBlocking {
        println("[Rustfisher] Junit test begins${System.currentTimeMillis()}")
        delay(1234)
        println("[Rustfisher] Junit test completed${System.currentTimeMillis()}")}}Copy the code

The results

[Rustfisher] Junit test started 1632401800686 [Rustfisher] Junit test ended 1632401801928Copy the code

IDEA may prompt no Tasks available. You need to change the test option to IDEA, as shown in the following figure.

Waiting for the

Sometimes you need to wait for the coroutine to complete. You can use the join() method. This method suspends the current coroutine until execution is complete. You need to use main() = runBlocking.

import kotlinx.coroutines.*

fun main(a) = runBlocking {
    println("[Rustfisher] Test wait")
    val job1 = GlobalScope.launch {
        println("job1 start")
        delay(300)
        println("job1 done")}val job2 = GlobalScope.launch {
        println("job2 start")
        delay(800)
        println("job2 done")
    }

    job2.join()
    job1.join() / / wait for
    println("End of test.")}Copy the code

Run the log

[rustfisher] Test wait for job1 start job2 start job1 done job2 done The test is completeCopy the code

Structured concurrency

With GlobalScope.launch, a top-level coroutine is created. As we saw in the previous example, it doesn’t use the main thread. The newly created coroutines are lightweight, but still consume some memory resources. If you forget to keep a reference to a newly started coroutine, it will continue to run.

We can use structured concurrency in our code.

In the example, we use the runBlocking coroutine builder to convert the main function to a coroutine. Coroutines started inside (scope) do not need to explicitly use joins.

Look at the following example:

import kotlinx.coroutines.*

fun main(a) = runBlocking<Unit> {
    println("Id of the main thread${Thread.currentThread().id}")
    launch { // Start a new coroutine 1 in the runBlocking scope
        println("Id of the thread where coroutine 1 resides${Thread.currentThread().id}")
        delay(300)
        println("Coroutine 1 completed execution")
    }
    launch { // Start a new coroutine 2 in runBlocking scope
        println("Id of the thread where coroutine 2 resides${Thread.currentThread().id}")
        delay(500)
        println("Coroutine 2 completed.")
    }
    println("Main thread completed")}Copy the code

Run the log

Main thread ID 1 Main thread completion ID of thread where coroutine 1 resides 1 Coroutine 2 resides 1 Coroutine 1 completes Execution coroutine 2 completes executionCopy the code

As you can see, there is no need to call Thread.sleep or delay to make the main Thread wait for a period of time to prevent the virtual machine from exiting.

The program waits for all of its coroutines to complete and then exits.

Scope builder

Declare your scope using the coroutineScope builder. It creates a coroutine scope and waits for all initiated coroutines to complete execution.

RunBlocking and coroutineScope look similar in that they both wait for their coroutine body and all child coroutines to end. The main differences are:

  • runBlockingMethod blocks the current thread to wait, yesThe conventional function
  • coroutineScopeIt just suspends, it frees up the underlying thread for other purposes, yesHang up function

The following example shows the characteristics of the scope builder. Main is a scope.

import kotlinx.coroutines.*

fun main(a) = runBlocking { // this: CoroutineScope
    launch {
        delay(200L)
        println("Coroutine 1 t${Thread.currentThread().id}")
    }

    coroutineScope { // Create a coroutine scope
        launch {
            delay(500L)
            println("Internal coroutine 2 minus 1 t${Thread.currentThread().id}")
        }

        delay(100L)
        println("Coroutine 2 PI t${Thread.currentThread().id}")
    }

    println("Primary mission completed.")}Copy the code

Run the log

Coroutine 2 T1 Coroutine 1 T1 Internal coroutine 2-1 T1 Main task completeCopy the code

Extract function refactoring

Will launch {… } internal code blocks are extracted into separate functions. The extracted function requires the suspend modifier, which is a suspend function.

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main(a) = runBlocking<Unit> {
    launch { r1() }
    println("DONE")}// Suspend the function
suspend fun r1(a) {
    delay(300)
    println("The function extracted by [Rustfisher]")}Copy the code

log

DONE [Rustfisher] extraction of the functionCopy the code

Coroutines are lightweight

We tried that before, creating a lot of coroutines, and it worked fine.

The following code can output many points

import kotlinx.coroutines.*

fun main(a) = runBlocking {
    for (t in 1.10000.) {
        launch {
            delay(t * 500L)
            print(".")}}}Copy the code

Global coroutines are like daemon threads

As we learned in the thread introduction, if there are only daemons left in the process, the virtual machine exits. In the previous example of printing rustfisher.com, you can also see that the program ends before the characters are printed.

An active coroutine started in GlobalScope does not keep the process alive. They are like daemon threads.

Here’s another example

import kotlinx.coroutines.*

fun main(a) = runBlocking {
    GlobalScope.launch {
        for (i in 1.1000000.) {
            delay(200)
            println("Coroutine execution:$i")
        }
    }

    delay(1000)
    println("Bye~")}Copy the code

log

Coroutine execution: 1 Coroutine execution: 2 Coroutine execution: 3 Coroutine execution: 4 Bye~Copy the code

Finally, we have a look at the idea of the full text

reference

  • Introduction to Kotlin coroutines
  • Introduction to Threads