The official document: kotlinlang.org/docs/corout… Github address: github.com/Kotlin/kotl… Coroutines example: play.kotlinlang.org/hands-on/In…

A list,

Coroutines are a concurrent design pattern that you can use on the Android platform to simplify code that executes asynchronously. A coroutine is a piece of code that can be suspended, and can be thought of as a lightweight thread. The relation between coroutines and threads: (1) Any coroutine can be created in a thread; (2) Execution, suspension and recovery of coroutines depend on threads, but there is no need to block threads when coroutines are suspended; (3) The coroutine need not specify a thread, that is, the coroutine can be suspended in one thread and then resumed in another thread;

1.1 Principle of coroutine suspension recovery

Each suspend decorated method or lambda expression adds an additional continuation-type argument to it when the code is called, and returns Any? Type.

/** * Interface representing a continuation after a suspension point that returns a value of type `T`. */
@SinceKotlin("1.3")
public interface Continuation<in T> {
    /** * The context of the coroutine that corresponds to this continuation. */
    public val context: CoroutineContext

    /** * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the * return value  of the last suspension point. */
    public fun resumeWith(result: Result<T>)
}

Copy the code

When the suspend function is suspended by a coroutine, it returns a special identifier, COROUTINE_SUSPENDED, which is essentially an Any; When the coroutine executes without suspension, it returns the result of execution or the exception thrown. So in order to support returns in both cases, we use Kotlin’s unique Any, right? Type.

1.2 Coroutine lightweight principle

(1) Small footprint Each thread has its own stack, usually 1MB. The minimum stack space allowed per thread in the JVM is 64K, while a simple coroutine in Kotlin occupies only a few tens of bytes of heap memory. But coroutine dependent Dispatchers have a limit on the number of threads. The coroutine is managed as suspend and resume by a callback-like object Continuation, which is added as the last argument to the function marked suspend keyword at compile time. The function, like any other object, is in the heap and is used to recover the coroutine. You don’t need thousands of MB of space in RAM to keep all the threads active. A typical 60-70 thread is created at Max using CommonPool and is reused (if a new coroutine is created, it will wait for another thread to complete).

2. Basic Usage

2.1 Adding a Dependency

First, add a dependency on the Kotlin plug-in in the project build.gradle

buildscript {
    ext.kotlin_version = '1.5.0
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"}}Copy the code

Then rely on the Kotlin library and Kotlin Coroutine in the Module:

apply plugin: 'kotlin-android'

dependencies {
	// The kotlin library
	implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
	// kotlin coroutine
   implementation 'org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.5.0 - RC'
}
Copy the code

2.2 Simple Example

// Launch is the builder of a coroutine. It launches a coroutine that can run independently
GlobalScope.launch {
	// Suspend the current coroutine; Delay is a suspend function :public suspend fun delay(timeMillis: Long); The suspension of coroutines does not block the current thread;
	delay(1000)
	Log.d("CoroutineTag"."Kotlin Coroutines World!") 
}
Log.d("CoroutineTag"."hello") 
Copy the code

The running results are as follows:

>hello
>Kotlin Coroutines World!
Copy the code

2.3 GlobalScope.launch source code analysis

In the 2.2 code, GlobalScope is an instance of CoroutineScope; Launch is the CoroutineScope extension function used to launch coroutines. The extension function is defined as follows:

/** * start a new coroutine and return the coroutine as Job; * The execution of the coroutine can be controlled by Job start(), cancle(), join() and other methods; * * Receives three arguments *@paramContext Coroutine context *@paramStart Specifies the startup mode of the coroutine. The DEFAULT value is CoroutineStart.DEFAULT *@paramBlock requires the suspended function * to be called@returnJob returns the newly created coroutine **/ as job
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope. () - >Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    // The suspension function passed in the argument is executed here
    coroutine.start(start, coroutine, block)
    return coroutine
}
Copy the code

Coroutine mechanism CoroutineScope, Coroutinescope.launch (), CoroutineContext, CoroutineStart

Third, CoroutineScope

Coroutine Scope is the Scope of Coroutine operation. Each asynchronous operation runs within a specific scope. Its definition is as follows:

/** * CoroutineScope defines CoroutineScope; * When a new CoroutineScope is created and a new coroutine is launched via coroutinescope.launch or coroutinescope.ayNC, the new CoroutineScope inherits the external CoroutineScope's Coroutin as well eContext; * /
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}
Copy the code

3.1 create CoroutineScope

Use the appropriate CoroutineScope and destroy it at the appropriate time to avoid memory leaks. For example, cancel coroutines in the CoroutineScope during Activity destruction to avoid memory leaks. CoroutineScope(1) CoroutineScope(); Generic CoroutineScope(); (2) MainScope(); The scope is UI life cycle, and the default scheduler is dispatchers.main; As follows:

class MyAndroidActivity {
    private val scope = MainScope()

    override fun onDestroy(a) {
        super.onDestroy()
        scope.cancel()
    }
}
Copy the code

The following describes the common CoroutineScope and the scenarios in which a CoroutineScope is created:

3.2 GlobalScope

Coroutines in GlobalScope scope can be executed after an App is started until the execution of the coroutine ends or is cancelled. They are often used to launch top-level coroutines that need to run during the application lifecycle and cannot be cancelled early. It is not recommended to use GlobalScope to start coroutines that no longer need to be executed after an Activity or Fragment has been destroyed.

3.3 CoroutineScope. Launch

GlobalScope.launch {
	Log.d("CoroutineTag"."GlobalScope") 
	launch {
		// This in coroutine is CoroutineScope
		Log.d("CoroutineTag"."coroutineScope")}}Copy the code

Launch creates a CoroutineScope that inherits the CoroutineContext of the external CoroutineScope.

3.4 CoroutineScope.aync

GlobalScope.launch {
	Log.d("CoroutineTag"."GlobalScope") 
	async {
		// This in coroutine is CoroutineScope
		Log.d("CoroutineTag"."coroutineScope")}}Copy the code

With CoroutineScope. Async, a CoroutineScope is created that inherits the CoroutineContext of the external CoroutineScope. The difference between coroutinescope.async and Coroutinescope.launch is that async returns Deferred and can retrieve the execution result of the coroutine.

3.5 coroutineScope

Within other coroutines or suspended functions, a coroutineScope can be created using coroutineScope, and the newly created coroutine in coroutineScope{} will be executed immediately, as shown below:

GlobalScope.launch {
	Log.d("CoroutineTag"."GlobalScope") 
	coroutineScope {
		// This in coroutine is CoroutineScope
		Log.d("CoroutineTag"."coroutineScope")}}Copy the code

CoroutineScope is a suspend suspend function, so it can only be called inside a coroutine or suspend function:

public suspend fun <R> coroutineScope(block: suspend CoroutineScope. () - >R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}
Copy the code

3.6 runBlocking

In a thread, coroutine, or suspended function, you can create a CoroutineScope with runBlocking and immediately execute the new coroutine in runBlocking{} as shown below:

GlobalScope.launch {
	Log.d("CoroutineTag"."GlobalScope") 
	runBlocking {
		// This in coroutine is CoroutineScope
		Log.d("CoroutineTag"."coroutineScope")}}Copy the code

Unlike coroutineScope, runBlocking blocks the current thread; RunBlocking is a normal method, so runBlocking can be called in a thread:

/ * * *@paramBlock suspends the function and returns T, which is the result of the coroutine */
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope. () - >T): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
        // create or use private event loop if no dispatcher is specified
        eventLoop = ThreadLocalEventLoop.eventLoop
        newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
        // See if context's interceptor is an event loop that we shall use (to support TestContext)
        // or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
        eventLoop = (contextInterceptor as? EventLoop)? .takeIf { it.shouldBeProcessedFromContext() } ? : ThreadLocalEventLoop.currentOrNull() newContext = GlobalScope.newCoroutineContext(context) }val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}
Copy the code

3.7 MVVM的CoroutineScope

Reference: developer.android.com/topic/libra…

For MVVM architecture, KTX provides LifecycleOwner, LiveData, ViewModel corresponding CoroutineScope; The usage method is as follows: (1) Add dependency

dependencies {
	// ViewModelScope
	implementation "Androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.2.0." "
	// LifecycleScope
	implementation "Androidx. Lifecycle: lifecycle - runtime - KTX: 2.2.0." "
	// liveData
	implementation "Androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0." "
}
Copy the code

(2) Usage examples

// 1.viewModelScope
class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.}}}// 2.lifecycleScope
class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            val params = TextViewCompat.getTextMetricsParams(textView)
            val precomputedText = withContext(Dispatchers.Default) {
                PrecomputedTextCompat.create(longTextContent, params)
            }
            TextViewCompat.setPrecomputedText(textView, precomputedText)
        }
    }
}
Copy the code

4. Coroutine startup mode

Coroutine Builder coroutines can be started in several ways:

4.1 CoroutineScope. Launch

Start a coroutine that does not block the calling thread and must be called in the CoroutineScope CoroutineScope. Return Job to indicate the newly created coroutine.

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    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

4.2 CoroutineScope. Async

A coroutine that starts but does not block the calling thread must be called in the CoroutineScope CoroutineScope. Returning Deferred indicates the newly created coroutine. Deferred is a subinterface of the Job.

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope. () - >T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
Copy the code

The difference between coroutinescope.async and Coroutinescope.launch is that async returns Deferred and can retrieve the execution result of the coroutine.

4.3 withContext

Specify a CoroutineContext and start a coroutine as follows:

GlobalScope.launch {
	withContext(Dispatchers.Default) {
	}
Copy the code

WithContext is a suspended function that can only be called inside other coroutines or suspended functions:

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope. () - >T
): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        // compute new context
        val oldContext = uCont.context
        val newContext = oldContext + context
        // always check for cancellation of new context
        newContext.checkCompletion()
        // FAST PATH #1 -- new context is the same as the old one
        if (newContext === oldContext) {
            val coroutine = ScopeCoroutine(newContext, uCont)
            return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
        }
        // FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
        // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
        if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
            val coroutine = UndispatchedCoroutine(newContext, uCont)
            // There are changes in the context, so this thread needs to be updated
            withCoroutineContext(newContext, null) {
                return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
            }
        }
        // SLOW PATH -- use new dispatcher
        val coroutine = DispatchedCoroutine(newContext, uCont)
        coroutine.initParentJob()
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}
Copy the code

4.4 coroutineScope {}

As shown in 3.2, coroutineScope{} creates a coroutineScope{} and executes the newly created coroutine in {} without blocking the current thread.

4.5 runBlocking {}

Starting a new coroutine and blocking the calling thread returns the result of the suspended function as the result of the coroutine, which is commonly used for main methods and tests (so not commonly used in Android development).

4.6 produce < > {}

Start a new coroutine and produce a set of data to a channel. This coroutine returns a ReceiveChannel from which other coroutines can receive data.

Fifth, CoroutineContext

5.1 define

CoroutineContext is a collection of elements. The main elements are the Job, which represents the coroutine, and the Dispatcher, which represents the coroutine (Jobs, Dispatchers, and CoroutineName all implement the Element interface). CoroutineScope encapsulates CoroutineContext:

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}
Copy the code

5.2 inheritance

When a new coroutine is launched via coroutinescope.launch, the new CoroutineScope inherits the external CoroutineContext, and the new coroutine Job becomes a child Job of the parent coroutine, so that the child Job is recursively cancelled when the parent Job is cancelled.

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    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

Job will not be cancelled unless: (1) GlobalScope. Launch coroutine; (2) A specific parent Job is passed in the launch;

5.3 combination CoroutineContext

In some cases, we need to customize the elements in CoroutineContext. We can use + to combine the elements in CoroutineContext:

launch(Dispatchers.Default + CoroutineName("test")) {
    println("I'm working in thread ${Thread.currentThread().name}")}Copy the code

Six, CoroutineStart

6.1 CoroutineStart. DEFAULT

Coroutinescope.launch () The DEFAULT startup mode for creating coroutines is coroutinestart.default, which is executed immediately.

6.2 CoroutineStart. LAZY

Coroutines with CoroutineStart.LAZY are not executed immediately and require a manual call to start(). You can set the startup mode to CoroutineStart.LAZY as follows:

val job = launch(start = CoroutineStart.LAZY) {
	// ...
}
// Call start manually
job.start()
Copy the code

Seven, the Job

The coroutinescope. launch function returns a Job object that represents an asynchronous task. The execution of the coroutine can be controlled by Job start(), cancle(), join() and other methods.

7.1 Simple Example

val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
    var nextPrintTime = startTime
    var i = 0
    while (isActive) { // cancellable computation loop
        // print a message twice a second
        if (System.currentTimeMillis() >= nextPrintTime) {
            println("job: I'm sleeping ${i++} ...")
            nextPrintTime += 500L
        }
    }
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit."Shell Hello World! DoneCopy the code

“Done” is displayed only after the job is executed.

7.2 to get the Job

A CoroutineContext has only one Job. You can obtain the Job in the CoroutineContext in the following way:

println("My job is ${coroutineContext[Job]}")
Copy the code

The output is:

My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
Copy the code

7.3 the Job status

Jobs have a life cycle and can be cancelled. A Job can also have hierarchical relationships. A Job can contain multiple child jobs. When the parent Job is cancelled, all child jobs are automatically cancelled. When the child Job is cancelled or an exception occurs, the parent Job is also cancelled.

/** ** A background job. ** ### job instances * The most basic instances of 'job' interface are created like this: * (1)Coroutine job is created with [launch][CoroutineScope.launch] coroutine builder. * It runs a specified block of code and completes on completion of this block. * (2)CompletableJob is created with a `Job()` factory function. * It is Completed by calling [CompletableJob.complete]. * * ### Job States * A Job has the following states: * * | **State** | [isActive] | [isCompleted] | [isCancelled] | * | -------------------------------- | ---------- | ------------- | ------------- | * | _New_ (optional initial state) | `false` | `false` | `false` | * | _Active_ (default  initial state) | `true` | `false` | `false` | * | _Completing_ (transient state) | `true` | `false` | `false` | * | _Cancelling_ (transient state) | `false` | `false` | `true` | * | _Cancelled_ (final state) | `false` | `true` | `true` | * | _Completed_ (final state) | `false` | `true` | `false` | * */
Copy the code

Suspend functions

The suspend function is called a suspend function and can only be called inside a coroutine or another suspend function, as shown below;

suspend fun doSomethingUsefulOne(a): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(a): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}
Copy the code

8.1 Sequential Execution

Suspend functions are executed sequentially, as follows:

val time = measureTimeMillis {
    val one = doSomethingUsefulOne()
    val two = doSomethingUsefulTwo()
    println("The answer is ${one + two}")
}
println("Completed in $time ms")
Copy the code

The results are as follows:

The answer is 42
Completed in 2017 ms
Copy the code

8.2 Asynchronous Execution

Sync is used to asynchronously execute the suspend function as follows:

val time = measureTimeMillis {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
Copy the code

The results are as follows:

The answer is 42
Completed in 1017 ms
Copy the code

8.3 Delay asynchronous execution

val time = measureTimeMillis {
    val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
    val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
    // some computation
    one.start() // start the first one
    two.start() // start the second one
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
Copy the code

The results are as follows:

The answer is 42
Completed in 1017 ms
Copy the code

Coroutine scheduling

Coroutine uses the CoroutineDispatcher to schedule which thread the Coroutine executes on; CoroutineDispatcher implements the CoroutineContext interface, which can be used as follows:

GlobalScope.launch {
	launch { // context of the parent, main runBlocking coroutine
    println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
    println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher 
    println("Default               : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
    println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")}// Other ways of starting coroutines can also specify Dispatcher
	async(Dispatchers.Default) {
	}
	withContext(Dispatchers.Default) {
	}
}
Copy the code

The running results are as follows:

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

Here are some common patterns of the CoroutineDispatcher:

1. Dispatchers.Default:

The default value when launching a coroutine, using thread pools internally; Dispatchers.Default limits the number of cores on the processor (2, 4, 6, 8, etc.);

2. Dispatchers.Main

Execute coroutines in the main thread

3. Dispatchers.IO:

Performs coroutines on IO threads, typically for network or I/O operations, and shares the thread pool with dispatchers.default; Dispatchers.IO limits up to 64 threads;

4. Dispatchers.Unconfined

Dispatchers.Unconfined cannot be used to create new threads. Coroutines executed and recovered using this method are executed on the current thread.

Data communication between coroutines

(1) The Deferred returned by coroutinescope.async represents the execution result of the coroutine, and the transmission of a single value between coroutines can use this method; (2) Channels are used to pass a series of values between coroutines.

10.1 channels

Channels are similar to BlockingQueue, except that BlockingQueue.put() and Blockingqueue.take block, while channel.send() and channel.receive do not.

val channel = Channel<Int>()
launch {
    // this might be heavy CPU-consuming computation or async logic, we'll just send five squares
    for (x in 1.. 5) channel.send(x * x)
}
// here we print five received integers:
repeat(5) { println(channel.receive()) }
println("Done!")
Copy the code

The running results are as follows:

1
4
9
16
25
Done!
Copy the code