preface

Coroutines are a difficult concept in Kotlin and the most important feature of its language. The concept of coroutines was not invented by Kotlin. It was proposed a long time ago, but it has been around for the last decade, especially with python, Go and other languages supporting coroutines, and I think Kotlin is most respected as the preferred language for Android development because of its support for coroutines. It’s hard to learn coroutines for the first time, just like the concept of threads is hard to understand when we first get to them, so we’ll start with theory, then work our way into applications, and then write as much as possible. Come on, coroutine!

1. Basic concepts of coroutines

What is a coroutine

The official definition of coroutines is as follows:

Coroutines simplify asynchronous programming by putting complexity into libraries. The program logic can be expressed sequentially in coroutines, and the underlying library takes care of the asynchrony for us. The library can wrap the relevant parts of user code as callbacks, subscribing to related events, and scheduling execution on different threads (or even different machines), while keeping the code as simple as sequential execution.

Briefly summarize the following points:

  • A coroutine is a program that can be suspended and resumed by the program itself (the most important part of the Kotlin coroutine is the suspension function, because the coroutine is suspended without blocking other threads)
  • Coroutines can be used to implement cooperative execution of multiple tasks
  • Coroutine can be used to solve the flexible transfer of asynchronous task control flow, solve the complex callback nesting, simplify the program structure, greatly improve the readability

Coroutines

  • Coroutines can reduce the design complexity of asynchronous programs. It is important to note that coroutines can not make code asynchronous, only asynchronous code simpler
  • Suspend and resume control the transition of execution flow (e.g. switching from UI thread to IO thread during network request and back to UI thread after callback)
  • Asynchronous logic can be written in synchronous code
  • Synchronous code is more flexible and easier to implement complex businesses than asynchronous code

Threads vs. coroutines

Thread refers to the operating system Thread, known as kernel Thread, Thread is preemptive operation, that is, when the time of one Thread is up, another Thread will run, in addition, the overhead of Thread switching or Thread blocking is relatively large; Coroutine refers to the language implementation of the Coroutine, which is mainly focused on collaboration, running on the kernel thread, that is, the Coroutine depends on the thread, so the Coroutine can be seen as a very lightweight thread. The suspension of coroutines is non-blocking and the developer is free to control the suspension and recovery of coroutines. Any number of coroutines can be created in a thread.

Summary: Coroutines simplify nested asynchronous programming into sequential expressions, greatly improve code readability, and provide a coroutine suspend method, which is a basically free and controllable alternative to blocking threads.

The basic elements of coroutines

Hang up function

What is a suspend function?

A function decorated with the suspend keyword is a suspend function, and a suspend is a thread scheduling operation that automatically cuts back after switching threads.

Suspend the function scope

Suspended functions can only be called from other suspended functions or coroutines.

Suspend and resume suspended functions

  • Suspend function calls contain the semantics of coroutine suspend
  • The suspended function returns with the semantics of coroutine recovery

The type of the suspended function

The only difference between the type of a suspended function and a normal function type is the suspend keyword. Such as:

Suspend fun foo(){} The corresponding type is suspend () -> Unit

Suspend fun foo(a:Int):String{TODO()} The corresponding type is suspend (Int) -> String

How does a suspend function do suspend recovery?

Suspend and resume suspended functions are actually implemented through the Continuation interface, which we define

The suspend fun foo(){} method actually looks like this:

fun foo(continuation: Continuation<Unit>): Any{ TODO()}
Copy the code

But the Continuation is passed for us by the compiler, so it is hidden in the function we define, so the suspended function can only be called in another suspended function or coroutine, because that is the only case where the Continuation exists.

The following API, which is open to Retrofit and GitHub, implements asynchronous callbacks as suspend functions in three main steps:

  • usesuspendCoroutineOf the suspended functionContinuation
  • Branch use to request a successful callbackContinuation.resume(value)
  • The request failed to be used by callbackContinuation.resumeWithException(t)

Start with the Retrofit initialization and write request interface methods:

val githubServieApi by lazy { val retrofit = retrofit2.Retrofit.Builder() .client(OkHttpClient.Builder().addInterceptor { it.proceed(it.request()).apply { println("request: ${code()}") } }.build()) .baseUrl("https://api.github.com") .addConverterFactory(GsonConverterFactory.create()) .build()  retrofit.create(GitHubApi::class.java) } interface GitHubApi { @GET("users/{login}") fun getUserCallback(@Path("login")  login: String): Call<User> } data class User(val id: String, val name: String, val url: String)Copy the code

Transform asynchronous callbacks into suspended functions

suspend fun getUserSuspend(name: String) = suspendCoroutine<User> {continuation -> // The call to enqueue will switch our operations to the I/O thread, so the true suspend must be an asynchronous call to resume, Including the switch to the other thread to resume or single-threaded event loop asynchronous execution / / if direct calls resume not hang githubServieApi getUserCallback (name). The enqueue (object: Callback<User> { override fun onFailure(call: Call<User>, t: Throwable) = continuation.resumeWithException(t) override fun onResponse(call: Call<User>, response: Response<User>) = response.takeIf { it.isSuccessful }? .body()? .let(continuation::resume) ? : continuation.resumeWithException(HttpException(response)) }) }Copy the code
Suspend fun main(){val user = getUserSuspend("Jeremyzwc")}  request: 200 User(id=20693153, name=Max_z, url=https://api.github.com/users/Jeremyzwc)Copy the code

2. Creation of coroutines

  • A coroutine is an executable program
  • The creation of coroutines usually requires a suspend function.
  • Coroutine creation also requires an API (createCoroutine)

Coroutines are created primarily through the createCoroutine method of continuations, which provides both with and without a receiver:

public fun <T> (suspend () -> T).createCoroutine(
    completion: Continuation<T>
): Continuation<Unit> 

public fun <R, T> (suspend R.() -> T).createCoroutine(
    receiver: R,
    completion: Continuation<T>
): Continuation<Unit>

Copy the code

The important thing to note here is that a coroutine requires two continuations, one of which is the Continuation instance that is called when the suspend function itself is executed, namely completion, which is passed in, The other Continuation is the return value Continuation

, which is the carrier of the created coroutine to which the Receiver suspend function is passed as the actual execution body.

Start of coroutines

The coroutine is started through the startCoroutine method of continuations, which also provides two options for starting with and without receiver:

public fun <T> (suspend () -> T).startCoroutine(
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}

public fun <R, T> (suspend R.() -> T).startCoroutine(
    receiver: R,
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}
Copy the code

To create a coroutine using the createCoroutine method, just call the Resume method to start the coroutine:

suspend {

}.createCoroutine(object :Continuation<Unit>{
    override val context: CoroutineContext
        get() = TODO("Not yet implemented")

    override fun resumeWith(result: Result<Unit>) {
        TODO("Not yet implemented")
    }
}).resume(Unit)
Copy the code

If you use the startCoroutine method, you start the coroutine directly by calling the suspend function. The startCoroutine method contains both the creation and start of the coroutine:

suspend { }.startCoroutine(object :Continuation<Unit>{ override val context: CoroutineContext get() = TODO("Not yet implemented") override fun resumeWith(result: Println ("Coroutinue end with $Result ")}})Copy the code

Coroutine context

  • Coroutine execution requires data to be carried
  • The index is CoroutineContextKey
  • The element is CoroutineContextElement

CoroutineContext is used as a data carrier to package and store data. In coroutines, CoroutineContext carries the name of the coroutine, exception handlers, and so on.

5. Interceptors:ContinuationInterceptor

  • ContinuationInterceptorIs an element of the coroutine context:
interface ContinuationInterceptor : CoroutineContext.Element
Copy the code
  • Of the coroutine where the coroutine context residesContinuationTo intercept
interface ContinuationInterceptor : CoroutineContext.Element {
   fun <T> interceptContinuation(continuation: Continuation<T>):                   Continuation<T>
}
Copy the code

The main function of intercepting a Continuation is to switch the thread. If the interceptContinuation method is passed in a Continuation

, then it returns a Continuation

. This is similar to the Okhttp interceptor that intercepts a request and returns a response.

To summarize the execution flow of continuations:

Typically we create and start a coroutine by:

suspend { 
    ...
}.createCoroutine(...)
Copy the code

suspend {… } is the body of the coroutine and the place where the coroutine logic is executed, which creates a SuspendLambda class that is the implementation of continuations in the standard library ContinuationImpl.

If the suspend {… }, SuspendLambda will be wrapped with SafeContinuation. SafeContinuation can also be thought of as an interceptor that intercepts SuspendLambda, The purpose of this SafeContinuation is to ensure that:

  • Resume is called only once
  • Calls made directly on the current thread call stack are not suspended, so true suspension must be thread – cutting

Something like the following:

Suspend {//SafeContinuation only appears at the start of a suspend}. CreateCoroutine (...) Suspend fun a() = suspendCoroutine<Unit> {thread {resume}}Copy the code

Resume of SafeContinuation resumes suspend {… }, so SuspendLambda’s resume method is also called.

Summary:

This article mainly introduces and studies the basic concepts and basic elements of Kotlin coroutine, and understands the process of Kotlin coroutine implementation. Basic concepts include:

  • Definition of coroutines: Suspend and resume
  • What coroutines do: Control flow transitions and asynchronous logic synchronization
  • Versus threads

The basic elements mainly include:

  • Continuation
  • Suspended functions (suspended function types and asynchronous callback overrides)
  • Creation of coroutines
  • Coroutine context (interceptor’s role)

In the next article we will document learning how to implement coroutines using the official coroutine framework.