preface

This article is mainly based on Kotlin, who wrote some of Kotlin’s articles in the past, which are relatively shallow and can be read by interested partners

Quickly switch to Kotlin for Android mode

Fully understand Kotlin, fast writing business

Threads are a love-hate relationship for Java folks. Threads can give us background operations that don’t block the main thread, but with that comes issues like thread safety and thread consumption that we have to deal with.

For Java development, the proper use of thread pools can help us deal with the cost of arbitrarily starting threads. In addition, the advent of the RxJava library also helps us to better de-threading the switch. So threads have been dominating my daily development…

Until, I touched on coroutines…

The body of the

Coroutines are components of computer programs that allow execution to be suspended and resumed. By 2003, however, many of the most popular programming languages, including C and its successors, did not support coroutines directly within the language or in their standard libraries. In today’s mainstream programming environment, threads are a suitable alternative to coroutines…

But! Now that it’s 2019, are coroutines really useless? ! Let’s get the fun out of coroutines from Kotlin today!

A, coroutines

Let’s talk a little bit about coroutines before we get into the game. Before we start coroutines, let’s talk about our everyday functions:

Functions, in all languages, are called hierarchically, so function A calls function B, function B calls function C, function C returns when it completes, function B returns when it completes, and finally function A completes.

So you can see that the function call is implemented through the stack.

The call to a function is always an entry, a return, in a clear order. The difference with coroutines is that the function itself is interruptible during execution, which means that after a break, it can switch to another function and return at an appropriate time to continue the unfinished work.

And this interruption is called suspension. Let’s think about callbacks again: register a callback function and execute it at the appropriate time.

  • Callbacks take an asynchronous form
  • Coroutines are synchronization

I don’t know if I got caught in the middle of something. Don’t worry, I look down, down more meng force, ha ha ~

Coroutines in Kotlin

Coroutines are a standard, as you can see from the Wiki. Any language can choose to support it.

Here is the document of about coroutines in Kotlin: kotlinlang.org/docs/refere…

What if we want to use coroutines in a project in Android? Very simple.

Assume that Kotlin dependencies can already be configured

2.1. Gradle is introduced

The introduction of coroutines in Android is very simple, just in Gradle:

apply plugin: 'kotlin-android-extensions'
Copy the code

Then add to the dependency:

implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.1.0." "
implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.1.0." "
Copy the code

2.2. Basic Demo

Take a look at the official basic demo:

// Start a coroutine
GlobalScope.launch(Dispatchers.Main) {
    // Execute a function with a delay of 10 seconds
    delay(10000L)
    println("World! -${Thread.currentThread().name}")
}
println("Hello-${Thread.currentThread().name}-")
Copy the code

Hello-main-world! Hello-main-world! – the main. And if you notice, these two outputs, they both print the main thread.

This code is executed on the main thread with a delay of 10 seconds and no ANR!

Of course, there are people here who say, WELL, I can do postDelay() with this Handler. Yes, our postDelay() is a callback solution. As we mentioned at the beginning, coroutines use synchronization to solve these problems.

Therefore, delay() in coroutines is also implemented through queues. But! It eliminates callbacks in the form of synchronization, making our code readable +100%.

2.2.1 Implementation of delay()

Warning… This will introduce a lot of the coroutine API from Kotlin. To avoid reading discomfort. This section is recommended to be skipped

Skip the summary:

Kotlin provides some apis that allow us to get rid of CallBack, essentially encapsulating CallBack to synchronize asynchronous code.

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    // Obviously, the implementation is still in the form of CallBack
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
    }
}

/** Returns [Delay] implementation of the given context */
internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ? : DefaultDelayinternal actual val DefaultDelay: Delay = DefaultExecutor
Copy the code

Use suspendCancellableCoroutine delay () () a hang coroutines, general control coroutines. The key to recovery in DefaultExecutor scheduleResumeAfterDelay (), The implementation is Schedule (DelayedResumeTask(timeMillis, Continuation)). The key logic is to put the DelayedResumeTask at the end of the DefaultExecutor queue. DelayedResumeTask is executed when the delay is reached. What is the implementation of this task?

override fun run(a) {
    // Restore the coroutine directly in the caller thread
    with(cont) { resumeUndispatched(Unit)}}Copy the code

2.3. Continue to understand

Now, let’s take a closer look at what the above code means.

First, delay () is called a suspend function, which can be suspended in the scope of the coroutine and does not block the execution of code outside the scope of the coroutine in the current thread. And the coroutine will resume execution of subsequent code in the coroutine scope when appropriate, resuming suspension.

Globalscope.launch (dispatchers.main) {} creates a global coroutine scope on the Main thread. Our delay(10000) is a suspend function, and when it is executed, the coroutine suspends the function. Println (“Hello-${thread.currentThread ().name}-“) out of coroutine scope has a chance to execute.

When the right time comes, 10,000 milliseconds later. The coroutine resumes the suspended function and continues with subsequent code.

thinking

Seeing this, I’m sure some of you thought to yourself, “Isn’t there a need for threads? After blocking operations, write directly in the suspended function?” . This is totally wrong! What coroutines provide is the ability to synchronize asynchronous code. The coroutine encapsulates the corresponding asynchronous API in user mode. Rather than really providing asynchronous capabilities. So if we do IO operations on the main thread coroutine, it will block the main thread as well.

GlobalScope.launch(Dispatchers.Main) { ... Network request /... Database operations with large amounts of data}Copy the code

Either throws NetworkOnMainThread or blocks the main thread. Because the above code is essentially executing on the main thread. So if we run code in a coroutine that blocks the current thread (such as an IO operation), it will still block the current thread. It’s possible to have the ANR that we see all the time.

Therefore, in this scenario, we need to call:

GlobalScope.launch(Dispatchers.IO) { ... Network request /... Database operations with large amounts of data}Copy the code

When we start a coroutine, we change a new coroutine context (this context switches the coroutine to the IO thread for execution). So we can start the coroutine on the child thread, just like we used to…

thinking

A lot of friends, I’m sure there’s a question here. Since we still use child threads to do background tasks… So what’s the point of coroutines? So let’s get into the meaning of coroutines.

The role of coroutines

3.1 reject CallBack

In our daily development, we often encounter such requirements: for example, in a Posting process, we need to log in first; After a successful login, we will send a document; We will update the UI after the successful Posting.

Pseudocode, a simple implementation of such requirements:

// The login pseudo-code. Pass a lambda, which is a CallBack
fun login(cb: (User) -> Unit) {... }// The pseudo-code of the post
fun postContent(user: User, content: String, cb: (Result) -> Unit) {... }/ / update the UI
fun updateUI(result: Result){... }fun ugcPost(content: String) {
    login { user ->
        postContent(user, content) { result ->
            updateUI(result)
        }
    }
}
Copy the code

With this requirement, we usually have two callbacks to complete the serial requirement. When you write this kind of code, have you ever wondered why the serial logic is done in **CallBack form (asynchronous) **?

You might say that these requirements need to be executed in the background using a thread and can only be retrieved through a CallBack.

So why do we have to use CallBack when we do background logic with threads? After all, from our logical point of view, these requirements are serial, and it is theoretically ok to execute the code sequentially. So coroutines come into play…

This asynchronous form of logic can be executed synchronously with the help of coroutines:

// Suspend the function, do not need any CallBack, our CallBack content, just as the return value of return
suspend fun login(a): User { ... }   
suspend fun postContent(user: User, content: String): Result { ... } 
fun updateUI(result: Result){... }fun ugcPost(content: String) {
    GlobalScope.launch {
        val user = login()
        val result = postContent(user, content)
        updateUI(result)
    }
}
Copy the code

This completes the nesting of layers of CallBack code, which can be written sequentially and logically.

Yeah, that’s one of the things that coroutines do.

  • 1. Of course, many people would say that a Future introduced in Java8 can perform similar serial execution. (anyway, are there many friends who didn’t upgrade to Java8?)…
  • 2, there must be some other friends who say, I can use Rx method, also can do this call…

Haha, absolutely. Because everybody’s trying to solve the same problem, but coroutines have other uses…

3.2. Convenient thread switching

Think of a very common requirement that we have, sub-thread network requests, data comes back and cuts to the main thread to update the UI.

RunOnUiThread () and RxJava make it easy to switch threads. Here we look at the way coroutines work:

GlobalScope.launch(Dispatchers.Main) {
    val result = withContext(Dispatchers.IO){
        // Network request and return request result. result }/ / update the UI
    updateUI(result)
}
Copy the code

Very straightforward logic, very straightforward code. Readability is +100%.

WithContext () is a handy way to switch threads in the context of coroutines and return execution results.

3.3. Convenient concurrency

Let’s look at the official code again:

import kotlinx.coroutines.*
import kotlin.system.*

fun main(a) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = doSomethingUsefulOne()
        val two = doSomethingUsefulTwo()
        println("The answer is ${one + two}")
    }
    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 did something useful here too
    return 29
}
Copy the code

The following output is displayed: The answer is 42

Completed in 2017 ms

Let’s assume that we take time to compute operations without any dependencies. So the best thing to do is to run them both in parallel. How do you get doSomethingUsefulOne() and doSomethingUsefulTwo() both executed?

Answer: async + await

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

Four,

This article mainly introduces coroutines. Coroutines are not a new concept and are supported by many languages.

Coroutines introduce the concept of suspension, allowing our functions to suspend at will and then execute again when we want to. Notifications give us the ability to write asynchronous code synchronously… It helps us write code more efficiently and more intuitively.

The end of the

Coroutines, there’s a lot to talk about. For more details due to space and time, I’ll leave it to the next article.

I am a fresh graduate, recently and friends maintain a public account, the content is that we in the transition from fresh graduate to the development of this way stepped on the pit, as well as our step by step learning records, if interested in friends can pay attention to it, together with fuel ~