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:
runBlocking
Method blocks the current thread to wait, yesThe conventional functioncoroutineScope
It 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