Tips 0: For simplicity, the term “coroutine” in this article refers specifically to “Kotlin coroutine”.

What is a coroutine? -ver2.0

The previous article made a lot of empty theoretical statements about how coroutines should be understood conceptually, but it is difficult to use concepts as a direct guide to development practice. So before we can actually use coroutines, we need to understand what coroutines are in the world of code.

Kotlin object-oriented programming language, threads can directly correspond to Thread objects, but there is no Coroutine class or interface for us to use, using coroutines is not very close to the intuition of old Java people, there is a certain learning cost. (An AbstractCoroutine type is marked InternalCoroutinesApi and should not be used externally.)

In practice, a coroutine is a piece of code that runs between suspend and resume. The life cycle of a coroutine is the process from suspending running code to the end of normal or abnormal recovery.

The life cycle of coroutines

Roughly speaking, there are three states of a coroutine (the states do not change very often as the coroutine runs, but can be further subdivided)

It looks a bit troublesome, but it’s not troublesome at all, and as you use it more, you’ll remember it naturally. Now let’s just look at how to create coroutines. The newly created coroutine is in an Incomplete state.

Create coroutines

Since Hello World, we’ve all learned how to create a coroutine by globalScope.launch {}, and this section starts with launch.

Coroutinescope.launch is used to create coroutines with no return value, which terminate automatically at the end of code execution. The return value of the launch function is of type Job, which can be used to get the status of coroutines, start and remove coroutines, and listen for the completion of coroutines.

Coroutinescope.async is another way to create coroutines that return values and end with an active call to await() to get the result. Async returns type Deferred

Launch and async take the same parameters as CoroutineContext, CoroutineStart, and the blocks that make up the contents of coroutines. CoroutineContext is the runtime environment for coroutines. It contains all the information needed by coroutines. CoroutineStart is used to control how the created coroutine is started. The DEFAULT coroutinestart. DEFAULT means immediate execution, so most of the sample code does not require an active call to start the coroutine.

Here are two simple examples, starting with a coroutine version of a very interesting sorting algorithm called Sleep sort.

private fun sort(nums: IntArray) {
    nums.forEach {
        lifecycleScope.launch {
            delay(it * 10L) // 1ms is prone to error, 1s is too long, compromise
            Log.w("CoroutineSampleActivity"."sort $it")}}}// test
override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    sort(intArrayOf(10.4.2.1.9.3.5.8.6.7))}Copy the code

Instead of creating Thread in the original algorithm, I started N coroutines in the loop. Optimizing thread.sleep () to delay() should be a performance breakthrough, and we suggest naming the coroutine version “delay Sort.” The coroutines created by Launch are left alone after code execution, and they do not need to be released manually.

Let’s take another example of async, this time with a useful practice code, and read an asset file to get the content string. File IO is a time-consuming operation and should not be performed in the UI thread. Asynchronous processing is a pain point to be solved by coroutines. Before looking at the code, you can think about how to write directly to create threads.

private suspend fun loadFile(assetName: String): String{
    val config = lifecycleScope.async(Dispatchers.IO) {
        val inputStream = assets.open(assetName)
        return@async inputStream.string() // is a custom ordinary extension function, independent of coroutines
    }
    return config.await()
}

// test
override fun onCreate(savedInstanceState: Bundle?). {
    super.onCreate(savedInstanceState)
    lifecycleScope.launch {
        val config = loadFile("config.json")
        Log.w("CoroutineSampleActivity"."loadFile $config")}}Copy the code

I just wrote the document. Just read it. The code is much cleaner with coroutines instead of callbacks and handlers passing messages across threads.

Understand coroutines and threads from practice

Looking back at the example above, which thread is the code on? In the read file example, we specify CoroutineContext as dispatchers. IO so as not to interfere with the UI thread. In the sort example, we don’t specify threads. This can be viewed by adding a Log.

Read files in a worker thread and all other code in the main thread. As you can see, the thread on which the code in the coroutine is executed is controlled by the Dispatcher. If dispatchers. IO is not specified in the code in Example 2, the UI thread is affected.

When it comes to concurrent programming, we have to consider thread-safety, and when we use coroutines, we need to be aware of the thread in which each line of code should be executed as we type. That said, using coroutines doesn’t mean you don’t need to understand threading.

Coroutines are described as “lightweight” in the coroutine website:

import kotlinx.coroutines.*

//sampleStart
fun main(a) = runBlocking {
    repeat(100 _000) { // launch a lot of coroutines
        launch {
            delay(5000L)
            print(".")}}}//sampleEnd
Copy the code

This means that creating 100,000 coroutines to run at the same time will not burden the system, and each coroutine will finish printing automatically after a delay of 5 seconds. If you create 100,000 threads at the same time, it’s almost impossible to run smoothly. While I don’t agree with the official view at this point, this comparison does show the difference between coroutines and threads in Kotlin, it just doesn’t quite prove “lightweight.”

From the above experiments and previous theoretical knowledge, delay will not block the current thread, and the 100,000 coroutine delay started by launch should be output in the same thread. So let’s simplify this a little bit, let’s do 100 coroutines plus logging and see what happens.

Smooth running.

Going back to the original example, 100,000 threads versus 2 threads certainly win hands down. But in real projects, no one abuses threads like that, we could have done the same thing with two threads, but the advantage of coroutines is that the code is easier to write.

The break

Creating and executing coroutines does some work, but it’s not ready to use coroutines. The CoroutineContext, CoroutineScope, Job, etc., which were briefly skipped above, need to be understood more deeply. You can see suspend in practice code, but you haven’t seen resume in pairs. Stay tuned for future articles.

(Has been goo-goo a little afraid of myself, try an incentive mechanism, likes + comments over20The next one will be at the post time7Updated within days)