What are coroutines?

Coroutines are components of a computer program that can be considered lightweight threads. Coroutines allow us to suspend the invocation of a function without blocking a thread. Let's imagine a case in which you need to perform a request on the server and display a progress bar until your app receives the response. A request is a long-term operation that should be performed asynchronously because a user interface should stay responsive. This is a common approach to running a new thread that uses a callback so that you're notified when the app receives a response. However, using code with callbacks looks unnatural, complex, and can lead to bugs.

Coroutines can be considered as a library that wraps a particular part of code with the creation of new threads and callbacks. This approach allows you to write asynchronous code in a way that looks as if it were sequentially executed.

The following example demonstrates this:

fun main(args: Array<String>) {
launch {
delay(500L)
println(Thread.currentThread().name)
}
println(Thread.currentThread().name)
Thread.currentThread().join()
}

The output is as follows:

main
ForkJoinPool.commonPool-worker-1

launch is a special function that creates and runs a new coroutine. This function isn't contained in the Kotlin Standard Library, and you need to include the kotlinx-coroutines-core library to use it. If you use the Gradle build tool, you should add the following line:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.0' 

This should be added to the dependencies section of your build.gradle file. We will look at the launch function in more detail a little bit later.

To support coroutines, Kotlin only contains the suspend keyword, which can be applied to a function or a lambda. The Kotlin Standard Library also contains base classes, and interfaces describe a coroutine in programming code, such as Continuation and CoroutineContext. The Continuation interface looks as follows:

public interface Continuation<in T> {
public val context: CoroutineContext
public fun resume(value: T)
public fun resumeWithException(exception: Throwable)
}

This interface represents a continuation after a suspension point. It contains the resume and resumeWithException functions, which are used to return the result value or an exception to the outer scope.

At the time of writing this book, the actual version of Kotlin is 1.2.60. However, in version 1.3.0, the Continuation interface is simplified and contains only one resumeWith(result: SuccessOrFailure<T>) function. resume and resumeWithException are extracted to extension functions and can still be used.

Each coroutine runs in a context that is represented by the CoroutineContext interface. A simplified version may look like this:

public interface CoroutineContext {

public operator fun <E : Element> get(key: Key<E>): E?

public fun <R> fold(initial: R, operation: (R, Element) -> R): R

public operator fun plus(context: CoroutineContext): CoroutineContext =
///......
}

public fun minusKey(key: Key<*>): CoroutineContext

public interface Key<E : Element>
}

The Element interface looks as follows:

public interface Element : CoroutineContext {

public val key: Key<*>

@Suppress("UNCHECKED_CAST")
public override operator fun <E : Element> get(key: Key<E>): E? =
if (this.key === key) this as E else null

public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)

public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key === key) EmptyCoroutineContext else this
}

The main elements are the instances of the Job and CoroutineDispatcher classes. Under the hood, coroutines use the usual threads, and CoroutineDispatcher decides which thread or threads are used by a coroutine. The launch function returns an instance of the Job class that implements the Element interface and can be used to cancel the execution of a coroutine. To demonstrate this, you can rewrite the preceding example as follows:

fun main(args: Array<String>) {
val job = launch {
delay(500L)
println(Thread.currentThread().name)
}
println(Thread.currentThread().name)
job.cancel()
Thread.currentThread().join()
}

The following is the output:

main
ForkJoinPool.commonPool-worker-1

The Job class also contains the join function, which can be invoked from another suspended function or a coroutine that suspends a callee function. We can use the join function to make the main thread wait until the job is complete. We should rewrite our example as follows:

fun main(args: Array<String>) = runBlocking {
val job = launch {
delay(500L)
println("Coroutine!")
}
println("Hello,")
job.join()
}

The output is as follows:

Hello,
Coroutine!

We can set up a breakpoint on the line, as shown in the following screenshot:

In the debug window, we can see that the main thread waits while a coroutine is running:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.44.229