** This is the 5th day of my participation in the August Changwen Challenge. For details, see: August Changwen Challenge **

preface

Kotlin coroutines are an amazing thing for a beginner to realize that you can use synchronous blocks of code to make asynchronous calls, but if you take a closer look, kotlin coroutines are essentially a functional programming style encapsulation of Java thread pools, which has many benefits. The first is that the functional + responsive style of programming avoids callback hell, which can be said to be a further evolution of implementing Promise, Future and other languages such as JS. The second is to avoid the performance penalty of too much thread switching due to developer error.

So let’s look at coroutines

More knowledge in the public number [Android development programming]

1. What are Coroutines

Coroutines are lightweight threads

  • Coroutines are a concurrent design pattern that you can use on the Android platform to simplify asynchronous execution of code.

  • A coroutine is an API that encapsulates method calls into a thread-like structure. Method calls are certainly lighter than thread switches; Encapsulated as a thread-like API, it looks like a thread (manually started, with various running states, able to work collaboratively, and able to execute concurrently). So in that sense, it’s a lightweight thread;

  • Coroutines are much more than just method calls, of course, because method calls cannot be suspended in the middle of a method’s execution and then resumed at the origin. This can be done using something like EventLoop. Imagine wrapping asynchronous code in the callback style or Promise/Future style into a synchronous style at the library level. The result is very close to a coroutine.

Threads run in kernel mode and coroutines run in user mode

Almost all the code we write is executed in user mode. Coroutines are just libraries provided by third parties to the operating system, and of course run in user mode. Threads, on the other hand, are something at the operating system level, running in kernel mode.

Coroutines are a thread framework

Kotlin’s coroutine library specifies the pool of threads in which the coroutine will run. We only need to operate on the coroutine, and the necessary thread switching is left to the library. In this sense, the coroutine is a thread framework

4. Coroutine implementation

Coroutine, as the name suggests, is the mutual cooperation of subroutine, multiple subroutine through a certain mechanism of mutual association, cooperation to complete a task. For example, a coroutine can be divided into multiple subroutines in the execution, each subroutine after the completion of the execution of the active suspension, waiting for the appropriate time to resume; When a coroutine is suspended, the thread can execute other subroutines, so as to achieve the purpose of multi-task processing with high utilization of thread. Coroutine can execute multiple tasks on a thread, while traditional thread can only execute one task. From the perspective of multi-task execution, coroutine is naturally lighter than thread.

5. Problems solved by coroutines

Write asynchronous code synchronously. Without coroutines, there are three main forms of API we can currently use: Pure callback styles (such as AIO), RxJava, and Promise/Future, all of which have the common problem of callback hell, which can only be solved by the number of lines in exchange for the number of levels, and which can be difficult to understand for programmers unfamiliar with asynchronous styles.

6, coroutine advantages

  • Lightweight: You can run multiple coroutines on a single thread because coroutines support suspension and do not block the thread running the coroutine. Suspending saves memory than blocking and supports multiple parallel operations.

  • Less memory leaks: Use structured concurrency mechanisms to perform multiple operations within a scope.

  • Built-in cancellation support: Cancellation operations are automatically propagated throughout the running coroutine hierarchy.

  • Jetpack integration: Many Jetpack libraries include extensions that provide full coroutine support. Some libraries also provide their own coroutine scope that you can use for structured concurrency;

Two, coroutine use

Rely on

dependencies {

Implementation ‘org. Jetbrains. Kotlinx: kotlinx coroutines – android: 1.3.9’

}

Coroutines need to run in a coroutine context, and there are three ways to start coroutines in a non-coroutine context

1, runBlocking {}

Starts a new coroutine and blocks the current thread until all of its internal and subcoroutine logic has executed.

This method is designed to allow libraries written in the Suspend style to be used in regular blocking code, often in main methods and tests.

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

Log.e(${mainLooper.thread.id}, ${mainLooper.thread.id})

test()

Log.e(TAG, “end of coroutine “)

}

private fun test() = runBlocking {

repeat(8) {

Log.e(TAG, “coroutine execution IT Thread ID: IT Thread ID: it Thread ID: {thread.currentThread ().id}”)

delay(1000)

}

}

A coroutine task started by runBlocking blocks the current thread until the coroutine finishes executing. When the coroutine finishes executing, the page is displayed.

2, GlobalScope. Launch {}

Starts a new coroutine within the scope of the application, and the life cycle of the coroutine is the same as that of the application. A coroutine launched in this way does not keep the thread alive, just like a daemon thread.

This is not recommended, especially in client-side scenarios where the component that started the coroutine has been destroyed but the coroutine still exists, which can lead to resource exhaustion in extreme cases.

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

Log.e(${mainLooper.thread.id}, ${mainLooper.thread.id})

val job = GlobalScope.launch {

delay(6000)

Log.e(${thread.currentThread ().id})

}

Log.e(TAG, “End of main thread “)

}

// The method in Job

job.isActive

job.isCancelled

job.isCompleted

job.cancel()

jon.join()

From the execution results, launch does not block the main thread.

Let’s summarize launch

Let’s look at the definition of the launch method:

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

}

As you can see from the method definition, launch() is an extension of CoroutineScope, which is simply the scope of the coroutine. The launch method has three parameters: 1. 2. Coroutine startup mode; 3. Coroutine body: A block is a function literal with a receiver, which is a CoroutineScope

①. Coroutine below above

  • Context can do a lot of things, including carrying arguments, intercepting coroutine execution, and so on. In most cases, we don’t need to implement the context ourselves, we just need to use an existing one. An important function of context is thread switching. Kotlin coroutines use a scheduler to determine which threads are used for coroutine execution. Kotlin provides a scheduler for us to use:

  • Dispatchers.Main: Use this scheduler to run a coroutine on the Main Android thread. It can be used to update the UI. Execute in the UI thread

  • Dispatchers.IO: This scheduler is optimized to perform disk or network I/O outside of the main thread. Execute in a thread pool

  • Dispatchers.Default: This scheduler is optimized to perform CPU-intensive work outside of the main thread. For example, sorting lists and parsing JSON. Execute in a thread pool.

  • Dispatchers.Unconfined: Execute directly on the calling thread.

  • The scheduler implements the CoroutineContext interface.

2. Startup mode

In the Kotlin coroutine, the startup mode is defined in an enumeration class:

public enum class CoroutineStart {

DEFAULT,

LAZY,

@ExperimentalCoroutinesApi

ATOMIC,

@ExperimentalCoroutinesApi

UNDISPATCHED;

}

A total of four startup modes are defined, which are described in the following table:

  • DEFAULT: The DEFAULT mode that executes the body of the coroutine immediately

  • LAZY: Run only if needed

  • ATOMIC: Executes the coroutine body immediately, but cannot cancel it before it starts running

  • UNDISPATCHED: Executes the body of the coroutine immediately on the current thread until the first suspend call

(3). Coroutines

The coroutine body is a no-arguments, no-return function type modified by the suspend keyword. A function decorated by suspend is called a suspended function and corresponds to the keyword resume. Note: a suspended function can only be called in coroutines and other suspended functions and cannot be used elsewhere.

Suspend functions suspend the entire coroutine, not just the suspend function, that is, when there are more than one suspend function in a coroutine, they are executed sequentially. Look at the following code example:

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

GlobalScope.launch {

val token = getToken()

val userInfo = getUserInfo(token)

setUserInfo(userInfo)

}

repeat(8){

Log.e(TAG,” $it”)

}

}

private fun setUserInfo(userInfo: String) {

Log.e(TAG, userInfo)

}

private suspend fun getToken(): String {

delay(2000)

return “token”

}

private suspend fun getUserInfo(token: String): String {

delay(2000)

return “$token – userInfo”

}

The getToken method suspends the coroutine, and the code behind the coroutine is never executed until the getToken suspension is restored. At the same time, coroutine suspension does not block the execution of other threads.

3.async/await:Deferred

Async is used in much the same way as launch, except that async returns Deferred and wraps the last one into this object. Async can support concurrency and is usually used with await. See the following example.

Async and await are two functions that we usually use in pairs;

Async is used to start an asynchronous coroutine task, and await is used to get the result returned at the end of the coroutine task, which is returned in a Deferred object

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

GlobalScope.launch {

val result1 = GlobalScope.async {

getResult1()

}

val result2 = GlobalScope.async {

getResult2()

}

val result = result1.await() + result2.await()

Log.e(TAG,”result = $result”)

}

}

private suspend fun getResult1(): Int {

delay(3000)

return 1

}

private suspend fun getResult2(): Int {

delay(4000)

return 2

}

Async does not block the thread, that is, getResult1 and getResult2 are performed at the same time, so the result takes 4s instead of 7s.

Three, coroutine exception

1. CancellationException thrown by the coroutine’s internal suspend method because of coroutine cancellation

2, the general exception, this kind of exception, there are two kinds of exception propagation mechanism

  • Launch: Automatically throws an exception to the parent coroutine, causing the parent coroutine to exit

  • Async: Expose exceptions to the user (by catching deffer.await() thrown exceptions)

example

fun main() = runBlocking {

val job = GlobalScope.launch { // root coroutine with launch

println(“Throwing exception from launch”)

Throw IndexOutOfBoundsException () / / we will be in the console print Thread defaultUncaughtExceptionHandler

}

job.join()

println(“Joined failed job”)

val deferred = GlobalScope.async { // root coroutine with async

println(“Throwing exception from async”)

Throw ArithmeticException() // prints nothing and relies on the user to call the wait

}

try {

deferred.await()

println(“Unreached”)

} catch (e: ArithmeticException) {

println(“Caught ArithmeticException”)

}

}

The results of

Throwing exception from launch

Exception in thread “DefaultDispatcher-worker-2 @coroutine#2” java.lang.IndexOutOfBoundsException

Joined failed job

Throwing exception from async

Caught ArithmeticException

Conclusion:

  • Coroutines can be cancelled and timed out, can be combined with suspended functions, the runtime environment specified in the coroutine, i.e., thread switching

  • The most common function of coroutines is concurrency, and a typical scenario for concurrency is multithreading.

  • Coroutines are designed to solve concurrency problems and make collaborative multitasking easier.

  • Kotlin coroutines can be understood as encapsulated thread pools or as a thread framework.