Guide to background work in Android. Part 5: Korutins in Kotlin

Original author: Vladimir Ivanov
  • Transfer

Kotlin Island

Previous texts in this series: about AsyncTask , about Loaders , about Executors and EventBus , about RxJava .

So, this hour has come. This is the article for which the entire series was written: an explanation of how the new approach works “under the hood”. If you don’t know how to use it yet, here’s some useful links to get you started:


While you are comfortable with the Korutins, you may wonder what enabled Kotlin to provide this opportunity and how it works. Please note that here we will focus only on the compilation stage: you can write a separate article about execution.

The first thing we need to understand is that there is no coruntine at all in runtime. The compiler turns the function with the suspend modifier into a function with the Continuation parameter . This interface has two methods:

abstract fun resume(value: T)
abstract fun resumeWithException(exception: Throwable)

Type T is the return type of your initial suspend function. And this is what actually happens: this function is executed in a specific thread (patience, we'll also get to it before), and the result is passed to the resume-function of that continuation, in the context of which the suspend-function was called. If the function does not get the result and throws an exception, then a resumeWithException is raised, passing an error to the calling code.

OK, but where did the continuation come from? Of course, from Korutinovsky builder! Let's look at the code that creates any korutinu, for example, launch:

public actual fun launch(
	context: CoroutineContext = DefaultDispatcher,
	start: CoroutineStart = CoroutineStart.DEFAULT,
	parent: Job? = null,
	block: suspend CoroutineScope.() -> Unit
): Job {
	val newContext = newCoroutineContext(context, parent)
	val coroutine = if (start.isLazy)
    	LazyStandaloneCoroutine(newContext, block) else
    	StandaloneCoroutine(newContext, active = true)
	coroutine.start(start, coroutine, block)
	return coroutine
}

Here the builder creates a corutin - an instance of the AbstractCoroutine class, which, in turn, implements the Continuation interface. The start method belongs to the Job interface. But finding the start method is very difficult. But we can come here from the other side. The attentive reader has already noticed that the first argument to the launch function is CoroutineContext, and by default it is assigned the value DefaultDispatcher. "Dispatchers" are the classes that control the execution of coruntine, so they are definitely important for understanding what is happening. Let's look at the DefaultDispatcher declaration:

public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool

So, in fact, this is CommonPool, although the java-docks tell us that this may change. And what is CommonPool?

This is a corutin dispatcher that uses the ForkJoinPool as the ExecutorService implementation. Yes, it is: in the end, all your lambda-korutiny - it's just Runnable, trapped in Executor with a set of tricky transformations. But the devil as always in the details.


Fork? Or join?

Judging by the results of the survey on my twitter, it’s necessary to briefly explain what the FJP is :)


First of all, ForkJoinPool is a modern executor designed for use with Java 8 parallel streams. The original task was effective parallelism when working with the Stream API, which essentially means splitting the streams to process part of the data and then merging when all data is processed. Simplifying, imagine that you have the following code:

IntStream
  	.range(1, 1_000_000)
  	.parallel()
  	.sum()

The amount of such a stream will not be calculated in one thread, instead, ForkJoinPool will recursively split the range into parts (first into 500,000 parts, then 250,000 each, and so on), calculate the sum of each part, and combine the results into one amount of. Here's a visualization of such a process:


Threads are separated for different tasks and re-combined after completion.

The efficiency of the FJP is based on the “work stealing” algorithm: when a particular thread runs out of tasks, it goes to the queues of other pool threads and steals their tasks. For a better understanding, you can see the report of Alexei Shipilev or overlook the presentation .

Great, we understood that our corutines are doing! But how do they get there?

This happens inside the CommonPool # dispatch method:

_pool.execute(timeSource.trackTask(block))

The dispatch method is called from the resume (Value: T) method in DispatchedContinuation. Sounds familiar! We remember that Continuation is an interface implemented in AbstractCoroutine. But how are they related?

The trick is inside the CoroutineDispatcher class. It implements the ContinuationInterceptor interface as follows:

public actual override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
	DispatchedContinuation(this, continuation)

See you You provide a simple block in corderin builder. You do not need to implement any interfaces that you don’t want to know about. This is all the Korutin library takes upon itself. It
intercepts the execution, replaces the continuation with DispatchedContinuation, and sends it to the executor, which guarantees the most efficient execution of your code.

Now the only thing left for us to understand is how dispatch is called from the start method. Let's fill this gap. The resume method is called from startCoroutine in the block's extension-function:

public fun <R, T> (suspend R.() -> T).startCoroutine(
    	receiver: R,
    	completion: Continuation<T>
) {
	createCoroutineUnchecked(receiver, completion).resume(Unit)
}

And startCoroutine, in turn, is called by the "()" operator in the CoroutineStart enumeration. Your builder takes its second parameter, and by default it is CoroutineStart.DEFAULT. That's all!

This is why I am fascinated by the approach of Corutin: it is not only a spectacular syntax, but also a brilliant implementation.

And for those who read to the end, they get an exclusive: video of my report “You don’t need a violinist: refuse RxJava in favor of Korutin in Kotlin” from the Mobius conference . Enjoy :)

Also popular now: