This is the first day of my participation in the August Text Challenge.More challenges in August
The first coroutine
In essence, coroutines are lightweight threads. They start with some coroutine builder. Here we start a new coroutine in [GlobalScope], which means that the life of the new coroutine is limited only by the life of the entire application
var globalScopeFun = GlobalScope.launch( context = Dispatchers.IO) {
delay(3000)
Log.i("coroutines"."GlobalScope: hello world")}class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.i("coroutines"."GlobalScope:start")
globalScopeFun// Coroutine code
Log.i("coroutines"."GlobalScope:end")}}Copy the code
GlobalScope:start
GlobalScope:end
GlobalScope:hello world
Copy the code
In the example above, a GlobalScope coroutine is launched and the log entry is delayed by three seconds. The result is that globalScope.launch {… } is replaced by thread {…… }, and delay(……) Replace with Thread. Sleep (…) To the same end. But there are differences between coroutines and threads. Coroutines are much better managed, can greatly improve concurrency efficiency, reduce code callbacks and improve readability.
- From the above code we can derive four concepts
- Suspend Fun: Delay is a suspend function “public suspend Fun Delay (timeMillis: Long)” that delays the coroutine without blocking the thread for a given amount of time and resumes the coroutine after a specified time.
- CoroutineScope: Defines the scope of the new coroutine. GlobalScope is the CoroutineScope implementation class that manages the life cycle of coroutines, which need to be started through the CoroutineScope
- IO is the abstract CoroutineContext that defines the maximum number of threads that Dispatchers can use. IO) coroutine scheduler
- CoroutineBuilder: CoroutineContext is launched with the coroutine constructor launch async declaration, which is an extension of CoroutineScope
Through the above four concepts, we analyze their principles and application scenarios one by one
Suspend fun suspends a function
- The error message
If you first replace globalScope. launch with thread, the compiler will raise the following Error: Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function
This is because delay is a special suspend function that does not block threads, but suspends coroutines and can only be used in coroutines.
public suspend fun delay(timeMillis: Long)
Copy the code
Delays coroutine for a given time without blocking a thread. Delay Delays coroutine for a given time without blocking a thread Delay a coroutine and not block a thread for a specified time. And resume the coroutine after a specified time, Delay functions are similar to Thread.sleep() in Java, and delay does not block threads because the life cycle of threads is controlled by the system, and delay of threads can be given to developers to control, and there is a saying in the source code: “This kickbacks kickbacks If the Job of the current coroutine is cancelled or completed while the suspended function is waiting, the function resumes immediately, so delay does not block.
The execution of the coroutine
fun main(a) = runBlocking {
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // Delay some time
println("main: I'm tired of waiting!")
job.cancel() // Cancel the job
job.join() // Wait for the job to finish
println("main: Now I can quit.")}Copy the code
The output of the program is as follows:
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
Copy the code
Once main calls job.cancel, we don’t see any output in the other coroutines because it’s canceled.
Coroutine scope
Let’s put together what we know about context, subcoroutines, and jobs. Suppose our application has an object with a life cycle, but this object is not a coroutine. For example, we wrote an Android application and started a set of coroutines in the Android Activity context to pull and update data using asynchronous operations, perform animations, and so on. All of these coroutines must be cancelled when the activity is destroyed to avoid a memory leak. Of course, we could also manipulate the context and job manually to combine the activity’s life cycle with its coroutines, but Kotlinx.coroutines provides a wrapper: the [CoroutineScope] abstraction. You should already be familiar with coroutine scope, since all coroutine builders are declared as extensions on top of it.
We manage the coroutine lifecycle by creating an instance of [CoroutineScope] and associate it with the activity lifecycle. CoroutineScope can be created by [CoroutineScope()] or by the [MainScope()] factory function. The former creates a generic scope, while the latter creates a scope for UI applications that use [dispatchers.main] as the default scheduler
class Activity {
private val mainScope = MainScope()
fun destroy() {
mainScope.cancel()
}
// Continue running......
Copy the code
Now we can use the defined scope to start the coroutine within the scope of the Activity. For this example, we started ten coroutines that delayed for different times:
// In the Activity class
fun doSomething() {
// In the example, 10 coroutines are started, each working for a different duration
repeat(10) { i ->
mainScope.launch {
delay((i + 1) * 200L) // Delay 200ms, 400ms, 600ms, etc
println("Coroutine $i is done")}}}}// End of the Activity class
Copy the code
In the main function we create the activity, call the test function doSomething, and destroy the activity after 500 milliseconds. This cancels all coroutines started from doSomething. We can observe this because after the destruction, the activity no longer prints messages, even if we wait a little longer.
import kotlinx.coroutines.*
class Activity {
private val mainScope = CoroutineScope(Dispatchers.Default) // use Default for test purposes
fun destroy(a) {
mainScope.cancel()
}
fun doSomething(a) {
// In the example, 10 coroutines are started, each working for a different duration
repeat(10) { i ->
mainScope.launch {
delay((i + 1) * 200L) // Delay 200ms, 400ms, 600ms, etc
println("Coroutine $i is done")}}}}// End of the Activity class
fun main(a) = runBlocking<Unit> {
val activity = Activity()
activity.doSomething() // Run the test function
println("Launched coroutines")
delay(500L) // Delay half a second
println("Destroying activity!")
activity.destroy() // Cancel all coroutines
delay(1000) // To visually confirm that they are not working
}
Copy the code
The output of this example looks like this:
Launched coroutines
Coroutine 0 is done
Coroutine 1 is done
Destroying activity!
Copy the code
As you can see, only the first two coroutines print messages, while the other coroutine calls job.cancel() a single time in activity.destroy ().
Using async
- Use async concurrency
Conceptually async is similar to launch. It starts a single coroutine, which is a lightweight thread that works concurrently with all other coroutines. The difference is that launch returns a Job with no result value, while Async returns a Deferred — a lightweight, non-blocking future that represents a promise that will provide a result at a later date. You can use.await() to get its final result on a Deferred value, but Deferred is also a Job, so you can cancel it if needed.
import kotlinx.coroutines.*
import kotlin.system.*
fun main(a) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")}suspend fun doSomethingUsefulOne(a): Int {
delay(1000L) // Suppose we do something useful here
return 13
}
suspend fun doSomethingUsefulTwo(a): Int {
delay(1000L) // Suppose we do something useful here
return 29
}
Copy the code
Its printout looks like this:
The answer is 42
Completed in 1017 ms
Copy the code
This is twice as fast, because two coroutines are executing concurrently. Note that concurrency with coroutines is always explicit.
- Lazy start async
Optionally, [async] can be made LAZY by setting the start parameter to [CoroutineStart.LAZY]. In this mode, the coroutine is started only when the result is obtained by [await], or when the [start] function of the Job is called. Run the following example
import kotlinx.coroutines.*
import kotlin.system.*
fun main(a) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
// Perform some calculations
one.start() // start the first
two.start() // Start the second
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")}suspend fun doSomethingUsefulOne(a): Int {
delay(1000L) // Suppose we do something useful here
return 13
}
suspend fun doSomethingUsefulTwo(a): Int {
delay(1000L) // Suppose we do something useful here
return 29
}
Copy the code
Its printout looks like this:
The answer is 42
Completed in 1017 ms
Copy the code
Thus, in the previous example the two coroutines defined here were not executed, but control rests with the programmer calling [start] exactly at the beginning of execution. We call one first, then two, and wait for the coroutine to complete.
Note that if we just call [await] in println without calling [start] in a separate coroutine, this will result in sequential behavior until [await] starts the coroutine execution and waits until it finishes, which is not the expected use case of inertia. The async(start = coroutinestart.lazy) use case is used to replace the LAZY function in the standard library when computing a value involving a suspended function.
infinally
Release resources in
We usually use the following method to handle a CancellationException that can be cancelled when it is cancelled. For example, try {… } the finally {… } expressions and Kotlin’s use function generally perform their finalizing actions when the coroutine is canceled:
import kotlinx.coroutines.*
fun main(a) = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)}}finally {
println("job: I'm running finally")
}
}
delay(1300L) // Delay some time
println("main: I'm tired of waiting!")
job.cancelAndJoin() // Cancel the job and wait for it to finish
println("main: Now I can quit.")}Copy the code
Join and cancelAndJoin wait for all terminations to complete, so running the example yields the following output:
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm running finally
main: Now I can quit.
Copy the code