An overview of the chapter

This section is mainly based on the official framework of Kotlin coroutine some API to experience the use of coroutine framework design ideas, this step for you to understand a good Kotlin coroutine framework source code has a great help, do not omit.

delay

Delay is the most commonly used function by any coroutine user, but how would you design it?

fun main(){ suspend { println("${System.currentTimeMillis()}") delay(5000) }.startCoroutine(object :Continuation<Unit>{ override val context: CoroutineContext get() = EmptyCoroutineContext override fun resumeWith(result: Result<Unit>) {println("${system.currentTimemillis ()}")}} thread:"+Thread.currentThread().id) Thread.sleep(10000) } suspend fun delay(time:Long,unit: TimeUnit =TimeUnit.MILLISECONDS){ if (time<=0){ return } val executor=Executors.newScheduledThreadPool(1){ Thread(it,"Scheduler"). Apply {isDaemon=true}} suspendCoroutine<Unit> {continuation -> executor.schedule({// Complete Resume (Unit)},time, Unit)}}Copy the code

Verify this result:

So basically it’s not that hard, even simple, but it’s essentially the same idea as when we call Thread.sleep in the Thread, except that we put sleep in the background Thread and when the sleep is finished we tell the original Thread that I’m done waiting and you can go back to work. That’s all.

Why do they do that? Because for a lot of UI systems based on event loops. You can easily cause the system to stall by sleeping on important UI threads

Notice that some of the coroutine apis introduced in the last article are really coroutine apis only! It’s in the official Kotlin package, but these apis are hard to use, and most people use kotlin’s coprogramming library! The Kotlinx-Corotines-core packages. The following is mainly to analyze several important knowledge points of coprogramming library.

Boot mode

Most of the time, when we get a scope, we will directly write lanuch. This is the most convenient way to write:

Many people may not realize that the start parameter can be configured and has different effects

To understand this concept, it is important to distinguish between immediate dispatch and immediate execution

Immediate scheduling: the scheduler receives scheduling instructions, but when and on which thread to execute depends on the scheduler’s own situation

That is, there is usually a period of time between the immediate dispatch state and the immediate execution state

With that in mind, it’s easy to figure out the four start effects:

Default: the coroutine is scheduled immediately after it is created. If the coroutine is cancelled before it is scheduled, the coroutine enters the state of cancelled response directly, that is, it may be cancelled before execution

Atomic: The coroutine will be executed immediately after it is created. The coroutine will not respond to cancellation until it reaches the first mount point, which means it must be executed.

Lazy: if it is cancelled before scheduling, it will not execute it if it enters the exception end state and you do not manually call methods such as start

In general, we can just focus on the Default and Lazy modes, especially the latter which can be useful in many scenarios. (Think back to thread development, were there many times when we defined a thread first and then started it at some point?)

The scheduler

The scheduler for coroutines, as its name suggests, schedules the thread on which your coroutine will be executed. Don’t think too lofty, behind the thread pool. The Default scheduler is generally suitable for background computing tasks. The IO scheduler naturally deals with IO related tasks. In addition, there is a Main scheduler, which is generally the UI thread scheduler, which is the so-called Main thread in Android development

Take a look at Default:

Note that the final Default scheduler is:

The IO scheduler looks like this:

In fact, the IO scheduler limits the concurrency of I/O tasks, because too many CONCURRENT I/O tasks occupy too many system resources. The default scheduler does not.

Since there is this distinction for the scheduler, where is the distinction made at the code level?

Continue to look at the source code, in fact, the so-called dispatch is ultimately going to the dispatch method,

And you end up in this dispatchWithContext method

Finally, the dispatch method of CoroutineScheduler is used to dispatch blocked and non-blocked tasks

Can coroutines always be cancelled?

Those who have used coroutines for a while may have called the cancel method, such as to exit a task that is in progress, but in some cases the task may not exit once it has started, such as the following official extension function:

This extension function is really very simple, mainly provides a copy stream operation, you can think of as a copy file operation

Let’s write a test demo:

fun main(){ val job= GlobalScope.launch(start=CoroutineStart.LAZY){ withContext(Dispatchers.IO){ val InputStream = FileInputStream (File ("/Users/wuyue/Downloads/office. PKG ")) println (" began to copy: ${System.currentTimeMillis()}") inputStream.copyTo(FileOutputStream(File("/Users/wuyue/Downloads/office2.pkg"))) Println (" End copy: ${system.currentTimemillis ()}")}} job.start() thread.sleep (1000) job.cancel() ${system.currentTimemillis ()}") ${system.currentTimemillis ()}" ${system.currentTimemillis ()}Copy the code

I’m actually just going to open a coroutine and copy a file and notice that my file is big and it’s about 1.8 GB so it’s going to take about 3 seconds to copy and I’m going to execute the cancel method 1s after I start executing

Logically, my coroutine should have exited, but the reality is that the log:

This timeline can be clearly seen, even if we call the cancel method, we will not exit the stream copy process

So is there a way to solve this problem?

suspend fun InputStream.copyToSuspend(out:OutputStream):Long{
    var bytesCopied: Long = 0
    val buffer = ByteArray(8 * 1024)
    var bytes = read(buffer)
    while (bytes >= 0) {
        yield()
        out.write(buffer, 0, bytes)
        bytesCopied += bytes
        bytes = read(buffer)
    }
    return bytesCopied
}

Copy the code

We’ll just copy the copy method and add a yield function that basically checks the state of the coroutine you’re working on, and if it’s canceled, we’ll just quit and give the other coroutine a chance to execute

Look again at the execution result:

As expected!

Sometimes you can also specify that the execution environment of a coroutine does not respond to cancel operations (like the try catch finanllay’s finanllay).

Just specify context as NonCancellable