Coroutines can be thought of simply as lightweight threads. Remember, the threads we learned before are very heavy and need to rely on the operating system scheduling to achieve switching between different threads. By using coroutines, the switching between different coroutines can be realized only at the programming language level, which greatly improves the running efficiency of concurrent programming.

Coroutines allow us to simulate the effects of multithreaded programming in single-threaded mode, where the suspension and recovery of code execution are completely controlled by the programming language, regardless of the operating system. This feature greatly improves the running efficiency of highly concurrent programs.

Why use coroutines?

  • Lightweight and efficient
  • Simple and easy to use
  • Asynchronous code can be written synchronously

The basic usage of coroutines

Add the dependent

First add the dependency library:

implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.3.1." "
// This dependency library is only used by Android projects, not pure Kotlin applications.
implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.1.1"
Copy the code

GlobalScope. Launch function

The easiest way to start a coroutine is to use the GlobalScope.launch function, which creates a scope for a coroutine. And you’re always creating a top-level coroutine, which is a little bit like a thread, because threads don’t have a hierarchy, they’re always top-level.

fun main(a){
    // The GlobalScope.launch function creates a top-level coroutine each time, which terminates when the application runs, so,
    // The application ends before the code in the block can run. The solution: delay the program for a while.
    GlobalScope.launch {
        println("codes run in coroutine scope")
        The delay function allows the current coroutine to run after a specified time.
        The delay function is a non-blocking suspend function that suspends the current coroutine without affecting the execution of other coroutines.
        The delay function can only be called from the scope of the coroutine or from other pending functions.
        // ----------------------------------------------------------------------
        Thread.sleep() blocks the current Thread, so that all coroutines running under that Thread are blocked.
        delay(1500)
        println("codes run in coroutine scope finished")  // Will not run
    }
    // If the code in the block does not finish running within 1 second, it will be forced to interrupt.
    Thread.sleep(1000)}Copy the code

RunBlocking function

You can use the runBlocking function to let an application end when all of the code in the coroutine has run.

Note: This function should normally only be used in a test environment, as using it in a formal environment can cause performance problems.

fun main(a){
    // runBlocking also creates a scope for coroutines,
    It does, however, guarantee that all coroutines and subcoroutines in the coroutine scope will block until they are all executed.
    runBlocking {
        println("codes run in coroutine scope")
        delay(1500)
        println("codes run in coroutine scope finished")}}Copy the code

Launch function

The launch function allows you to create multiple coroutines, which must be called within the scope of the coroutine. Second, it creates child coroutines within the scope of the current coroutine. The characteristic of subcoroutines is that if the coroutine in the outer scope terminates, all subcoroutines in that scope also terminate.

fun main(a){
    runBlocking {
        launch {
            println("launch1")
            delay(1000)
            println("launch1 finished")
        }
        launch {
            println("launch2")
            delay(1000)
            println("launch2 finished")}}// Result:
    // launch1
    // launch2
    // launch1 finished
    // launch2 finished

    // Test performance
    test()
}

fun test(a){
    val start = System.currentTimeMillis()
    runBlocking {
        // The loop creates 100,000 coroutines
        repeat(100000){
            launch {
                println(".")}}}// View the elapsed time
    val end = System.currentTimeMillis()
    println(end - start)

    // Run result: 433 ms
}
Copy the code

Suspend the keyword

Code written in the launch function has coroutine scope, but extracted into a separate function has no coroutine scope.

You can use the suspend keyword, which allows you to declare any function as a suspend function, and suspend functions can call each other.

suspend fun printDot(a){
    println(".")
    delay(1000)}Copy the code

CoroutineScope function

But the suspend keyword does not provide coroutine scope, which means you cannot call functions like launch. You can use the coroutineScope function, which is also a suspend function and therefore can be called in any other suspend function. It inherits the external coroutine scope and creates a subscope.

fun main(a){
    runBlocking {
        printDot()
    }
}

suspend fun printDot(a) = coroutineScope{
    launch {
        println(".")
        delay(1000)}}Copy the code

The coroutineScope function is also similar to the runBlocking function in that it guarantees that all code and child coroutines in its scope will block the current coroutine until they have all executed.

fun main(a){
    // create a coroutine scope
    runBlocking {
        // create a subcoroutine scope
        coroutineScope {
            // create a subcoroutine
            launch {
                for (i in 1.10.){
                    println(i)
                    delay(1000)
                }
            }
        }
        println("coroutineScope finished")
    }
    println("runBlocking finished")

    // Result:
    / / 1
    / / 2
    / / 3
    / / 4
    / / 5
    / / 6
    / / 7
    / / 8
    / / 9
    / / 10
    // coroutineScope finished
    // runBlocking finished
}
Copy the code

However, the coroutineScope function blocks only the current coroutine and does not affect other coroutines or any threads, so it does not cause any performance problems. The runBlocking function is not recommended for use in real projects because it blocks the current thread and is called in the main thread because it may cause the interface to freeze.


More scope constructors

You learned several scope constructors above, all of which can be used to create a new coroutine scope. However, the globalscope. launch and runBlocking functions can be called from anywhere, the coroutineScope function can be called from the coroutineScope or from the pending function, and the launch function can only be called from the coroutineScope.

Job object

Of these, GlobalScope.launch is not generally recommended because it is a top-level coroutine that is created each time, because it is too expensive to administer and requires calling all of the created coroutines’ cancel() methods one by one when the Activity closes. Unless it’s very clear that you want to create a top-level function.

/** ** */
fun main(a){
    Both globalScope. launch and launch return a Job object.
    val job = GlobalScope.launch { 
        // Handle concrete logic
    }
    job.cancel()
}
Copy the code

The more commonly used writing method in actual projects is as follows:

fun main(a){
    // Create the Job object and pass it to the CoroutineScope() function.
    val job = Job()
    The CoroutineScope() function returns a CoroutineScope object, which can then be invoked to create a coroutine.
    val scope = CoroutineScope(job)
    // All coroutines created thus are associated under the scope of the Job object, and cancel() is called once,
    // All coroutines in the same scope can be cancelled, thus greatly reducing the cost of coroutine management.
    scope.launch { 
        // Handle specific logic
    }
    job.cancel()
}
Copy the code

Async function

A new coroutine can be created when the launch function is called, but the launch function can only be used to execute a piece of logic, but it can’t get the result of the execution because its return value is always a Job object. In this case, an async function can be used.

The async function, which must be called from the coroutine scope, creates a new child coroutine and returns a Deferred object with await() of the object.

fun main(a){
    runBlocking {
        val result = async {
            5 + 5
        }.await()
        println(result)
    }
}
Copy the code

After the async function is called, the code in the code block starts executing immediately. When await() is called, if the code in the block is not finished executing, await() blocks the current coroutine until the result of the async function can be obtained.

fun main(a){
    runBlocking {
        val start = System.currentTimeMillis()
        val result = async {
            delay(1000)
            5 + 5
        }.await()
        val result2 = async {
            delay(1000)
            4 + 6
        }.await()
        println("result id ${result + result2}.")
        val end = System.currentTimeMillis()
        println("cost ${end - start} ms.")
        
        // Result:
        // result id 20.
        // cost 2021 Ms. // It takes 2 seconds, which indicates that it is serial.}}Copy the code

Change the above code to parallelism to improve efficiency:

fun main(a){
    runBlocking {
        val start = System.currentTimeMillis()
        val result = async {
            delay(1000)
            5 + 5
        }
        val result2 = async {
            delay(1000)
            4 + 6
        }
        // Call await() only if the result of async is needed.
        println("result id ${result.await() + result2.await()}.")
        val end = System.currentTimeMillis()
        println("cost ${end - start} ms.")

        // Result:
        // result id 20.
        // cost 1021 ms. // It takes 1 second, which indicates that it is indeed parallel.}}Copy the code

WithContext function

A special scope constructor is a suspend function that can be thought of as a simplified version of the async function.

fun main(a){
    runBlocking {
// val result1 = async { 5 + 5 }.await()

        // This is equivalent to the above code, except that it is mandatory to specify a thread parameter.
        // ------------------------------------------------------------
        // Calling withContext immediately executes the code in the block, blocking the current coroutine.
        // When the execution is complete, the result of the last line is returned as the value.
        val result = withContext(Dispatchers.Default){
            5 + 5
        }
        println(result)
    }
}
Copy the code

The significance of the thread parameter is that, although you don’t need to turn on multiple threads to perform concurrent tasks like traditional programming, you only need to turn on multiple coroutines in one thread. However, this does not mean that it is no longer necessary to start threads. For example, in Android, a time-consuming task needs to be performed in a child thread, so the coroutine we started in the main thread cannot do the job. At this point, a specific running thread can be specified for the coroutine through the thread parameter.

Thread parameters can be selected from the following three values:

  • Dispatchers.Default: Use a thread policy with low concurrency by Default. When the code to be executed is computation-intensive and high concurrency may affect the efficiency of the task, use dispatchers. Default.
  • Dispatchers.IO: Use a thread strategy with high concurrency, in order to support higher concurrency when the code to be executed is mostly blocked and waiting, such as when executing network requests.
  • Dispatchers.Main: indicates that child threads are not started, and this value is only used in Android projects, executing code in the Android Main thread.

In fact, all of the coroutineScope constructors we learned above can specify such a thread parameter except for the coroutineScope function, but the withContext function is mandatory.


Simplify the way callback is written

The main purpose of coroutines is to greatly improve the efficiency of concurrent programming. But in fact, coroutines in Kotlin can also optimize the way traditional callbacks are written, making code much cleaner.

Simplifies callback of HttpURLConnection network requests:

The /** * callback mechanism basically relies on anonymous classes to implement it, but this is often cumbersome to write. * For example, the following code needs to write anonymous class implementations as many times as the number of places a request is made. * /
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener{
        override fun onFinish(response: String) {
            // Get the details returned by the server
            showResponse(response)
        }

        override fun onError(e: Exception) {
            // The exception is handled here}})In the past, there was probably nothing simpler to write. In Kotlin, however, you can use the suspendCoroutine function to continue the simplification. * The suspendCoroutine function, which must be called from the coroutine scope or suspension function, takes a Lambda expression argument, suspends the coroutine immediately when *, and then executes the code in the Lambda expression in a normal thread. A Continuation argument is passed to the argument list of a Lambda expression, and calling its resume() or resumeWithException() can resume execution of the coroutine, * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * define a hang function request () * /
suspend fun request(address:String):String{
    return suspendCoroutine { continuation ->
        HttpUtil.sendHttpRequest(address,object:HttpCallbackListener{
            override fun onFinish(response: String) {
                continuation.resume(response)
            }

            override fun onError(e: Exception) {
                continuation.resumeWithException(e)
            }
        })
    }
}

/** * Is also a suspend function, so when request() is called, the current coroutine is suspended immediately and waits for the network request to succeed or fail before resuming. This allows you to retrieve the response data for an asynchronous network request without using a callback, and if the request fails, it goes directly to a catch statement. * /
suspend fun getBaiduResponse(a){
    try {
        val response = request("https://www.baidu.com")
        // Process the corresponding data of the server
    }catch (e:Exception){
        // Handle exceptions}}Copy the code

Simplify callbacks for Retrofit network requests:

// The traditional way
val appService = ServiceCreator.create<AppService>()
appService.getAppData().enqueue(object : Callback<List<AppBean>>{
    override fun onResponse(call: Call<List<AppBean>>, response: Response<List<AppBean> >) {
        // Get the data returned by the server
    }

    override fun onFailure(call: Call<List<AppBean>>, t: Throwable) {
        // Handle exceptions}})/** ** is equivalent to the above written */
suspend fun getAppData(a){
    // For a try catch, each request is now made once.
    // You can also choose not to handle the exception, and then the exception will be thrown up one layer at a time, until the position is handled by the function of one layer,
    // It is also possible to have a single try catch in a unified entry function to simplify the code.
    try {
        val appList = ServiceCreator.create<AppService>().getAppData().await()
    }catch (e:Exception){
        // Handle exceptions}}/** * define a pending function await() * Since different Service interfaces return different data types, use the generic approach. * It declares a generic T and defines the function as an extension of Call
      
        so that * all Retrofit network request interfaces that return a Call can Call this function directly. * /
      
suspend fun <T> Call<T>.await(a): T{
    // Suspends the current coroutine, and because of the extension function, you now have the context of the Call object and can Call enqueue() directly.
    return suspendCoroutine { continuation ->
        enqueue(object : Callback<T>{
            override fun onResponse(call: Call<T>, response: Response<T>) {
                val body = response.body()
                if(body! =null)continuation.resume(body)
                else continuation.resumeWithException(RuntimeException("response body is null"))}override fun onFailure(call: Call<T>, t: Throwable) {
                continuation.resumeWithException(t)
            }
        })
    }
}
Copy the code

Note: Suspended functions can only be called from coroutine scope or other suspended functions, and their use is limited. However, with proper project architecture, it is also easy to apply the code of various coroutines to a common project.


note

References:

Line 1 of code (Version 3)

The official documentation

Official Chinese translation site

Welcome to follow wechat official account:No reason also