There are several concepts in coroutines: CoroutineScope, Job,CoroutineContext
CoroutineScope CoroutineScope
There are two ways to create a scope. The common launch is just an extension of the CoroutineScope function
// No result is returned
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope. () - >Unit
): Job
// A result is returned
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope. () - >T
): Deferred<T>
Copy the code
One of the concepts of structured concurrency is that coroutine Cop should be created whenever you start a coroutine on any page and control its life cycle. Child Coroutine executions can be created in a top-level Coroutine code block, which is not completed until all child coroutines have been executed and returned.
The introduction of the KTX class in Android already provides CoroutineScope for unspecified lifecycles, such as viewModelScope and lifecycleScope.
val job = GlobalScope.launch { // Start a new coroutine and keep a reference to the job
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // Wait until the subcoroutine execution is complete
fun main(a) = runBlocking<Unit> { // Start executing the main coroutine
GlobalScope.launch { // Start a new coroutine in the background and continue
delay(1000L)
println("World!")
}
println("Hello,") // The main coroutine executes immediately here
delay(2000L) // Delay 2 seconds to keep the JVM alive
}
Copy the code
RunBlocking and coroutineScope are similar in that they both wait for the coroutine body and child coroutines to end. The difference is that the runBlocking method blocks the current thread waiting (the normal function), whereas the coroutineScope simply suspends, freeing the underlying thread for use elsewhere (the suspended function).
CoroutineContext CoroutineContext
A coroutine context is a collection of various elements, the primary of which is the Job in the coroutine.
The elements that define the coroutine context have the following parts:
- Job, manages the life cycle of coroutines
- CoroutineDispatcher, which dispatches tasks to appropriate threads
- CoroutineName, the name of the coroutine, used for debugging
- CoroutineExceptionHandler, handle an uncaught exception
Some of these elements have Default values: dispatchers. Default for CoroutineDispatcher, CoroutineName, coroutine
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
// The parent of the new coroutine is scope
val result = async {
// The parent of the new coroutine here is the scope. Launch coroutine above
}.await()
}
Copy the code
Parent coroutine responsibilities: A parent coroutine always waits for all child coroutines to finish executing.
In the inheritance structure of coroutines, each coroutine has a parent coroutine. When the child coroutine is created, the inherited CoroutineContext is the parent CoroutineContext. The parameters passed to the coroutine builder take precedence over those in the inheritance context.
The final coroutine scheduler for CoroutineContext is dispatchers.io because it is overridden by parameters in the coroutine builder.
Job
Job represents a coroutine. You can manage the life cycle of coroutines by joining () and cancel()
- Join () : blocks the current thread until the child thread finishes executing
- Canenl () : execution of the coroutine
- CancelAndJoin () : This combines calls to cancel and join
All pending functions in Kotlinx. coroutines are cancelable. They check for cancellation of coroutines and throw a CancellationException on cancellation, which cannot be canceled if the coroutine is performing a computational task and is not checked for cancellation.
Combine elements in context
If you define multiple elements in a coroutine, you can use the + operator to do so. Since CoroutineContext contains a list of elements, when a new CoroutineContext is created, the elements to the right of the + will override the elements to the left.
// We can explicitly specify a scheduler to start the coroutine and also explicitly specify a name:
launch(Dispatchers.Default + CoroutineName("test")) {
println("I'm working in thread ${Thread.currentThread().name}")}I'm working in thread DefaultDispatcher- worker-1@test# 2
Copy the code
It is designed to change the exception handling of the coroutine scope
val a = CoroutineScope(SupervisorJob() + coroutineContext).launch(handler) {
delay(1000)
System.err.println("(Main.kt:51) ${Thread.currentThread()}")}Copy the code
The scheduler
Used to specify the thread in which the coroutine code block is executed. Kotlin provides several Default coroutine schedulers, Default, Main, and Unconfined
Unconfined : I'm working in thread main
Default : I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread
main runBlocking : I'm working in thread main
Copy the code
Coroutine cancellations and timeouts
If the coroutine has already been executed, it cannot be cancelled. Check job. IsActive or ensureActive in the coroutine to determine whether the coroutine isActive, and specify CancellationException by cancelling the parameter of the function
fun Job.ensureActive(a): Unit {
if(! isActive) {throw getCancellationException()
}
}
Copy the code
while (i < 5) {ensureActive ()... }Copy the code
useyield()
In disappear assist cheng
The first thing yield does is check to see if the task is complete, and if the Job is, it throws a CancellationException to end the coroutine. Yield should be invoked first in a scheduled check, just like ensureActive mentioned earlier.
Deferred is a Job that can also be cancelled. A JobCancellationException is thrown for a deferred invoked await method that has been cancelled.
valDeferred = async {... } deferred.cancel()val result = deferred.await() // throws JobCancellationException!
Copy the code
Cancellation of coroutines requires code implementation, so make sure you detect cancellation in your code to avoid extra useless work.
Run code blocks that cannot be cancelled
When you need to suspend a cancelled coroutine, you can wrap the corresponding code in withContext(NonCancellable) {… }, and use the function withContext and NonCancellable context.
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)}}finally {
withContext(NonCancellable) {
println("job: I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
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.")
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
job: And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.
Copy the code
timeout
In practice, the overwhelming reason to cancel a coroutine is that it may time out.
//withContext
/ / reference and launched a single coroutines cancelled tracking sell TimeoutCancellationException after the delay
fun main(a) = runBlocking {
try {
withTimeout(2000L) {
repeat(100 _000) {
println("launch_____$it")
delay(1000L)}}}catch (error: TimeoutCancellationException) {
println("error:$error")
}
println("Game is over")}Copy the code
//withTimeoutOrNull Executes a timeout operation by returning NULL instead of throwing an exception:
fun main(a) = runBlocking {
try {
val result = withTimeoutOrNull(2000L) {
repeat(100 _000) {
println("launch_____$it")
delay(1000L)}"Done"
}
println("result____$result")}catch (error: TimeoutCancellationException) {
println("error:$error")
}
println("Game is over")}Copy the code
Coroutines abnormal
With the container job, the child thread’s failure is not expected to affect other child coroutines, and the container is also designed to let the child coroutine handle itself instead of propagating exceptions.
val scope = CoroutineScope(SupervisorJob())
scope.launch {
// Child 1
}
scope.launch {
// Child 2
}
Copy the code
val scope = CoroutineScope(Job())
scope.launch {
supervisorScope {
launch {
// Child 1
}
launch {
// Child 2}}}Copy the code
In both cases, child#1 fails, and neither scope nor child#2 is cancelled.
The container is only designed to handle one of two scopes the container is designed to handle: the scope that is created using the container or the CoroutineScope(container container).
If the container is created by the parent coroutine using scope.launch, the container is not designed to handle the container.
When we handle an exception, we usually call a try/catch for the following cases:
supervisorScope {
val deferred = async {
codeThatCanThrowExceptions()
}
try {
deferred.await()
} catch(e: Exception) {
It is designed to be handled by the coroutine itself and is able to handle exceptions
}
}
coroutineScope {
try {
val deferred = async {
codeThatCanThrowExceptions()
}
deferred.await()
} catch(e: Exception) {
If an exception occurs on a coroutine created by another coroutine, it will be automatically propagated to the parent coroutine, regardless of your coroutine builder
// So no catch}}Copy the code
CoroutineExceptionHandler
Coroutines in the exception handler is CoroutineExceptionHandler CoroutineContext an optional element, it can help you handle uncaught exception.
val handler = CoroutineExceptionHandler {
context, exception -> println("Caught $exception")}// The Handel handler must be placed on the parent coroutine to catch exceptions
val scope = CoroutineScope(Job())
scope.launch(handler) {
launch {
throw Exception("Failed coroutine")}}The container is handled by the subroutine
supervisorScope {
val child = launch(handler) {
println("The child throws an exception")
throw AssertionError()
}
println("The scope is completing")}Copy the code
Capture conditions:
- Is thrown by a coroutine that can automatically throw an exception (
launch
Rather thanasync
) - In the context of a coroutine or root coroutine (
CoroutineScope
The direct subcoroutine of orsupervisorScope
)
reference
- Kotlin website
- Coroutines : First things first
- The most comprehensive Kotlin coroutine