2. What is this presentation
about?
• For an in depth introduction to coroutines look at
Introduction to Kotlin Coroutines and Deep Dives into
Coroutines on JVM from Roman Elizarov.
• What offers kotlinx-coroutines.
3. What are coroutines?
• Light-weight threads, is it correct?
• Instances of suspendable computation!
• Quite an old concept from 1960s.
• Present in multiple languages —> JS, Python, C#
• However, how are implemented is quite different.
4. What is the goal of
coroutines?
• Make concurrency simple.
• Make concurrent code explicit.
• From
val future : Future<Result> = httpRequest(request)
val result : Result = await httpRequest(request) // await keyword
• To
val future : Deferred<Result> = async { httpRequest(request) }
val result : Result = httpRequest(request)
5. What experimental means?
KotlinConf Keynote Recap
Even though coroutines are still labeled as an experimental
feature, we’d like to clarify the exact meaning of this status.
Coroutines are fully ready to be used in production, […].
The reason why we keep the experimental status is that it gives us
the ability to iterate on the design. Note that, even if we do make
changes to the API, the current API will remain supported, even
though it will be marked as deprecated, and we will provide the
necessary migration tools. According to our current plans, the
experimental status of coroutines will be removed in Kotlin 1.3.
12. launch
• launch is used to build a Job, a coroutine of which we
are not interested in its result.
• Some useful methods:
• cancel()
• join()
• cancelAndJoin()
• invokeOnJobCompletion(..)
13. async
• async is used to build a Deferred, a non-blocking
cancellable future.
• Implements Job interface.
• await() is a non-blocking method for collecting the
result of the coroutine.
14. async - example
fun main(args: Array<String>) = runBlocking {
val sequentialTime = measureTimeMillis {
val twoSquare = getSquareFromServer(2)
val threeSquare = getSquareFromServer(3)
println("2^2 = $twoSquare || 3^2 = $threeSquare")
}
val asyncTime = measureTimeMillis {
val twoSquare = async { getSquareFromServer(2) }
val threeSquare = async { getSquareFromServer(3) }
println("2^2 = ${twoSquare.await()} || 3^2 = ${threeSquare.await()}")
}
println("Sequential time = $sequentialTime ms”) // ~2000ms
println("Async time = $asyncTime ms”) // ~1000ms
}
suspend fun getSquareFromServer(value: Int): Int {
delay(1, TimeUnit.SECONDS) // simulate a non-blocking call to the server
return value * value
}
15. produce
• produce is used to create a ProducerJob, for streams
of elements production.
• Sends elements inside a channel.
• Channel can be buffered or unbuffered.
16. produce - example
suspend fun produce_example() {
val dotGenerator = produce(newSingleThreadContext("dot-thread")) {
println("dotGenerator is in thread [${Thread.currentThread().name}]") // working in thread [dot-thread]
while (isActive) {
send(".")
delay(50)
}
}
val dotChannel = dotGenerator.channel
val dotPyramid = produce {
println("dotPyramid starting in thread [${Thread.currentThread().name}]") // [commonPool-worker-n]
(1..10).map {
async(coroutineContext) {
println("[$it] in thread [${Thread.currentThread().name}]") // [commonPool-worker-n]
buildString { (1..it).map { append(dotChannel.receive()) } }
}
}.map { send(it.await()) }
delay(50)
println("dotPyramid ending in thread [${Thread.currentThread().name}]") // [commonPool-worker-n]
}
while (dotPyramid.isActive) {
try {
println(dotPyramid.receive())
} catch (e: ClosedReceiveChannelException) {
println("The pyramid channel has been closed")
}
}
println("Is dotGenerator active? ${dotGenerator.isActive}")
}
17. actor
• actor produces an ActorJob, that processes a stream of
messages.
• Used to implement the Actor Model.
• An actor is a computational entity that, in response to a message
it receives, can concurrently:
• send a finite number of messages to other actors;
• create a finite number of new actors;
• designate the behavior to be used for the next message it receives.
18. actor - example
sealed class Http
data class HttpRequest(val request: CompletableDeferred<Int?>) : Http()
data class HttpResponse(val response: Int) : Http()
suspend fun actor_http() {
val httpRandomNumActor = httpActor()
sendPeriodicallyRandomInt(httpRandomNumActor)
(1..10).forEach {
delay(10)
val response = CompletableDeferred<Int?>()
httpRandomNumActor.send(HttpRequest(response))
println("Sent request $it")
println("[$it] --> response ${response.await()}")
}
}
suspend fun httpActor() = actor<Http>(newFixedThreadPoolContext(3, "http-actor")) {
var latestResponse: Int? = null
for (msg in channel) {
when (msg) {
is HttpRequest -> msg.request.complete(latestResponse)
is HttpResponse -> {
latestResponse = msg.response
println("Updated response on [${Thread.currentThread().name}]")
}
}
}
}
private fun sendPeriodicallyRandomInt(httpRandomNum: ActorJob<Http>) {
launch {
while (isActive) {
httpRandomNum.send(HttpResponse(Random().nextInt()))
println("Sent response on [${Thread.currentThread().name}]")
delay(30)
}
}
}
19. Generators and yield
• Possibility to have Generators using Kotlin Sequence.
• yield() and yieldAll() to suspend the computation.
• Part of Kotlin stdlib.
20. Generators - example
import kotlin.coroutines.experimental.buildSequence
fun generator() {
fibonacci().take(5) // Lazily evaluated as expected
for (num in fibonacci()) {
if (num == 13) break
}
}
fun fibonacci() = buildSequence {
var prev = 1
println("Current value is $prev")
yield(prev)
var next = 1
while (true) {
println("Current value is $next")
val tmp = next
yield(tmp)
next = prev + tmp
prev = tmp
}
}
21. Select
• select offers the possibility to choose the first result
returned by two or more suspending functions, that
returns a SelectClause#N.
• Biased on the order of the functions.
• selectUnbiased solves this issue.
22. Select - example
suspend fun randomNumProducer(id: Int) = produce {
while (isActive) {
send("[${Thread.currentThread().name}] - Produce $id")
val sleep = Random().nextInt(50).toLong()
delay(sleep)
}
}
suspend fun selectBetween(channel1: ReceiveChannel<String>, channel2: ReceiveChannel<String>) {
select<Unit> {
channel1.onReceive {
println(it)
}
channel2.onReceiveOrNull {
println(it ?: "[${Thread.currentThread().name}] - channel is closed")
}
}
}
suspend fun select_example() {
val producer1 = randomNumProducer(1)
val producer2 = randomNumProducer(2)
repeat(5) {
selectBetween(producer1, producer2)
}
producer2.cancel()
repeat(3) {
selectBetween(producer1, producer2)
}
}
23. What else?
• Non-blocking Mutex.
• Channel, like the one used by actor and produce.
• Suspending function withTimeout,
withTimeoutOrNull and run.
25. What is in there?
• kotlinx-coroutines-android, offers the UI context.
• kotlinx-coroutines-javafx, offers the JavaFx context.
• kotlinx-coroutines-swing, offers the Swing context.
26. UI - example
fun ui_example() {
launch {
val image = async { loadImage() }
val title = async { loadTitle() }
display(image.await(), title.await())
}
}
suspend fun display(image: Image, title: String) {
run(UI) {
// display on UI thread
}
}
suspend fun loadImage(): Image =
downloadImage() // non-blocking operation for downloading the image
suspend fun loadTitle(): String =
downloadTitle() // non-blocking operation for downloading the title
28. What is in there?
• kotlinx-coroutines-jdk8, integration with
CompletableFuture.
• kotlinx-coroutines-nio, integration with JDK7+ Async IO.
• kotlinx-coroutines-guava, integration with
ListenableFuture.
• kotlinx-coroutines-quasar, integration with Quasar
library.
29. CompletableFuture
• The coroutine builder future.
• Extension functions await().
• [!] CompletableFuture should be used only for
interacting with existing libraries that use it, otherwise
prefer Deferred<T>.
• [!!] get() is blocking, await() is not.
30. CompletableFuture -
example
suspend fun future_example() {
val readString = future(context = newSingleThreadContext("read-string")) {
stringWithDelay()
}
val readString2 = future {
stringWithDelay()
}
println("${readString.await()} - ${readString2.await()}")
}
suspend fun stringWithDelay(): String {
println("Current thread is [${Thread.currentThread().name}]")
delay(10)
println("Current thread is [${Thread.currentThread().name}]")
return "future"
}
32. Recap
• Should you use coroutines in production? YES!!
• Should you use coroutines everywhere? NO!!
• They still relay on OS threads —> if you call blocking
code inside a coroutine, you block the thread on which
the coroutine is running —> Be Careful!!
• They make sense for IO bound tasks, not CPU bound
tasks!!