4. RxKotlin VS Kotlin coroutines - Reactive
Construire des systèmes qui répondent bien à leurs utilisateurs
Déploiement
………………
Dizaine de
serveurs
MaintenanceTemps de
réponse
secondes
millisecondes
Mobile, clusteurs
cloud se basant
sur des milliers de
processeurs multi
coeurs
Des heures et
des heures
• Révision de patterns
existants
• Réflexions autour de
nouveaux patterns et
architectures
100%
disponibles
Volumétrie
des données
Quelques Go
Mesuré en
Petabytes
5. RxKotlin VS Kotlin coroutines - Reactive
Reactive Manifesto 16 Septembre 2014
6. RxKotlin VS Kotlin coroutines - Reactive
4 Principes du “Reactive Manifesto” :
Bien réagir aux utilisateurs : en terme de temps de réponse et en terme d’utilisabilité
(usability)
Bien réagir face à la panne (failure) : le système doit rester disponible (replication,
isolation, delegation)
Bien réagir face à la variation de la charge (workload) : le système doit pouvoir
augmenter ou réduire les ressources qui lui sont allouées selon la montée ou la baisse
de charge qu’il subit
Bien réagir face à ses inputs : Les systèmes réactives reposent sur le passage de
messages de façon asynchrone (asynchronous message-passing), ce qui ne crée pas de
dépendance directe entre les composants du système
7. RxKotlin VS Kotlin coroutines - Reactive
Les différentes extensions Reactive :
.NET JAVA
Portée
JAVASCRIPT
ReactiveX regroupe un ensemble d’api permettant la programmation asynchrone en utilisant des streams
observables
RxJava : Java RxScala : Scala Rx.Net : .NET RxClojure : Clojure RxSwift : Swift
RxCpp : Cpp UniRx : C# (Unity) RxLua : Lua RxGo : Go http://reactivex.io
8. RxKotlin VS Kotlin coroutines - Reactive
Le modèle Actor :
1973 : Carl Hewit Un modèle de traitement concurrent (toutes les communications se passent entre des
entités appelées “Actors” à travers l’envoi de messages)
L’un des premiers langages à supporter la programmation reactive, en utilisant les
“Actors” comme son architecture principale
Akka Actors, Akka Streams : Scala et Java
Reactive ?
“Actors” sont asynchrones, non bloquants, supportent la communication par messages, il peuvent utiliser
plusieurs cores sur une seule machine ainsi que sur plusieurs noeuds, ils fournissent des méchanismes de
supervision
10. RxKotlin VS Kotlin coroutines - RxJava
Ben Christensen David Karnok
MTA SZTAKI (Institute for Computer Science and Control)
1.0 : Novembre 2014 2.0 : Novembre 2016
La base pour plusieurs ports d’extensions réactives se basant sur la JVM :
RxScala, RxKotlin, RxGroovy, RxClojure (Reactive extensions, appelé aussi Rx ou ReactiveX)
11. RxKotlin VS Kotlin coroutines - RxJava
Observable : Type qui représente un flux (Stream) de données ou évènements
Il peur représenter 0, 1 à plusieurs (voir infini) valeurs ou évènements à travers le temps
Composants
Observer : Le composant “Observable” passe de façon séquentielle ses valeurs /
évènements au composant Observer
Operator : Le composant “Operator” opère sur un “Observable” et redonne en sortie un
“Observable”, ce qui permet d’appliquer les “Operator” l’un après l’autre à travers une chaîne,
les composants “Operator” peuvent se placer ainsi en chaîne entre le composant “Observable”
et le composant “Observer”
Observer Observable (Source)1 : subscribe
N : push events
onNext : Data
onError
onCompleted (1.0) / OnComplete (2.0)Terminal events
12. RxKotlin VS Kotlin coroutines - RxJava
Flowable : Il arrive que le composant “Observable” émette les données plus rapidement
qu’il n’en faut de temps pour le composant “Observer” de les traiter, dans ces cas là le
composant “Flowable” est utilisé (BackPressure), il permet de mieux gérer le flux de données
afin de garantir leur traitement.
Composants
Subscriber : Lorsque le composant “Flowable” est utilisé à la palce d’un composant “
Observable”, le composant “Subscriber” vient remplacer le composant “Observer”, il permet de
se désinscrire de la source de données (“Flowable”)
BackPressureStrategy : la stratégie permettant de gérer les flux de données émis
à partir du composant “Flowable”
• MISSING : aucune stratégie n’est appliquée
• ERROR : peut générer une exception MissingBackpressureException si le composant “Subscriber” ne peut plus
traiter les données émises à partir du composant “Flowable”
• BUFFER : Stockages des données émises dans une structure “Queue” jusqu’à ce que le “Subscriber” puisse les
traiter à nouveau (peut généer une exception OutOfMemoryException si la structure grandit)
• DROP : ignorer les données émises tant que le “Subscriber” est incapable de traiter
• LATEST : garder la dernière émission jusqu’à ce que le “Subscriber” soit à nouveau opérationnel
13. public static void main(String[] args) {
Flowable<Integer> source = Flowable.create(emitter -> {
for (int i=0; i<=1000; i++) {
if (emitter.isCancelled())
return;
emitter.onNext(i);
}
emitter.onComplete();
}, BackpressureStrategy.BUFFER);
source.observeOn(Schedulers.io()).subscribe(System.out::println);
sleep(1000);
}
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Créer une instance de Flowable
Emet des entiers entre 0 à 1000
Stratégie BackpressureStrategy.BUFFER est utilisée
Le composant “Flowable” souscrit à un composant
“Subscriber” qui prend la forme d’une expression
Lambda (System.out.println)
RxKotlin VS Kotlin coroutines - RxJava
15. RxKotlin VS Kotlin coroutines - Kotlin
2012
Les IDE développés par JetBrains se basaient sur JAVA (en grande
partie)
Alternatives : Clang , Scala (tous les deux écartés)
La naissance de Kotlin : concise, expressive, toolable, interoperable
Langage statique (types statiques)
Inspiré par Java, Scala, C#, Groovy
Runtimes cible : JVM, JavaScript, Native (LLVM sous étude)
1.0 Release : 15 Février 2016 (utilisé dans aproximativement
10 produits JetBrains)
16. RxKotlin VS Kotlin coroutines - Kotlin
RxKotlin
bindings
Librairie légère se basant sur le feature (extension
functions) de Kotlin, permettant ainsi d’utiliser
RxJava avec celui-ci
Extension functions : en similitude avec le langage
de programmation C#, Kotlin permet d’étendre une
classe en lui rajoutant de nouvelles fonctionnalités
sans avoir le besoin d’en hériter et sans utiliser le
patter “Decorator”
18. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Pourquoi les coroutines ?
• Traitement et programmation asynchrones (callbacks imbriquées , …)
• Generators (yield en C# et Python)
• UI Asynchrone
Cas d’utilisation
• Futures / promises
• Chanels
• Concurrence basée sur les “actors”
• Process background nécessitant l’intervention utilisateur
19. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Terminologie des coroutines
• Instance d’un traitement qui peut être suspendu à certains points puis reprendre l’exécution plus tard
(peut même reprendre son exécution au niveau d’un autre Thread)
• Conceptuellement similaire à un Thread, dans le sens où elle prend un bloc de code à exécuter et
où elle a le même cycle de vie de celui-ci, mais n’est rattaché à aucun Thread
• Il ressemble à un Future / Promise, il peut être complété en donnant un résultat ou en retournant
une exception
Coroutine
20. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Terminologie des coroutines
Suspending function
• Fonction qui est marquée en utilisant le modifier suspend
• Elle peut suspendre l’exécution du code sans bloquer l’exécution du Thread courant
• Elle ne peut être appelée qu’à partir d’une fonction marquée comme suspend ou bien à partir des
“suspending lambdas”
• La librairie standard définit les “suspending function” de base qui sont utilisées pour redéfinir toutes
les autres “suspending function”
21. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Terminologie des coroutines
Suspending lambda
• Bloc de code qui peut être exécuté dans une coroutine
• Lambda normale + marqué par le modifier suspend
• Les même propriétés qu’une “suspending function”
22. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Terminologie des coroutines
Suspending function type
• Type de fonction pour les “suspending functions” et les “suspending lambdas”, qui est marqué par le
modifier suspend
• suspend () -> Int : type de « suspending functions » qui ne prend pas d’arguments et retourne un Int
(suspend fun foo(): Int)
23. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Terminologie des coroutines
Coroutine builder
• Fonction qui prend comme argument une “suspending lambda” et en crée une coroutine et donne
accès optionnellement au résultat sous une certaine forme
• La librairie standard contient des « coroutines builder » permettant de créer les autres « coroutines
builder »
Suspension Point
• Point durant l’éxécution de la coroutine, où l’éxécution est suspendue (syntaxiquement, c’est
l’invocation d’une “suspending function”), mais la suspension ne commence que la “suspending
function” de la librairie standard est invoquée à partir de la “suspending function” invoquée au niveau
de la coroutine
24. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Terminologie des coroutines
Continuation
• État de la coroutine suspendu au niveau du “suspension point”
• Représente conceptuellement le reste de l’exécution de la coroutine après le point de suspension
Coroutine Context : contexte persistent de la coroutine (structure de données clé-
valeur entre set et map, chaque élément a sa propre clé, les clés sont comparées par référence)
Coroutine Scope : permet au code composant la coroutine d’accéder au contexte de
celle-ci ainsi que son état de fermeture (cancellation)
Coroutine Dispatcher : composant de base permettant de dispatcher l’exécution de la
routine au niveau threads
25. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Terminologie des coroutines
Coroutine Start : définit le comportement de lancement de la coroutine, utilisé comme
paramètre pour les fonctions coroutines builder
Channels : moyens non-bloquants (non-blocking) permettant aux coroutines de
communiquer entre elle des flux d’éléments
26. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Résponsabilité du compilateur
• Le compilateur n’est responsable que du support des « suspending functions », « suspending
lambdas » et les types « suspending function » y correspondant
• State machines : le compilateur crée uniquement une classe par « suspending function » ou
« suspending lambda », qui peut avoir un nombre arbitraire de points de suspensions dans son body
(les states correspondent aux points de suspension)
val a = a()
val y = foo(a).await() // suspension point #1
b()
val z = bar(a, y).await() // suspension point #2
c(z)
3 states au niveau de ce bloc de code :
Initial : avant tout point de suspension
Après le premier point de suspension
Après le deuxième point de suspension
27. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Résponsabilité du compilateur
val a = a()
val y = foo(a).await() // suspension point #1
b()
val z = bar(a, y).await() // suspension point #2
c(z)
29. RxKotlin VS Kotlin coroutines - Kotlin coroutines
fun launch(
context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job (source)
Lance une nouvelle coroutine sans bloquer le thread
courant et retourne une référence vers une instance Job
incluant la coroutine, celle-ci est terminé lorsque le job
résultant est terminé
• context : le contexte de la nouvelle coroutine peut être spécifié explicitement (par défault à
DefaultDispatcher
• start : par défault la coroutine est imédiatement programmée pour l’exécution
• block : le bloc de code représentant la coroutine
Coroutine builders
30. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Coroutine builders
Lance une nouvelle coroutine et retourne son résultat Future
sous format de Deferred, la coroutine est fermée lorsque
l’objet résultant est fermé
fun <T> async(
context: CoroutineContext = DefaultDispatcher,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> (source)
fun <E> produce(
context: CoroutineContext = DefaultDispatcher,
capacity: Int = 0,
block: suspend ProducerScope<E>.() -> Unit
): ProducerJob<E> (source)
Lance une nouvelle coroutine et produit un flux de valeurs
en les envoyant vers un channel puis retourne une
référence vers la coroutine en tant que ProducerJob, cet
objet peut être utilisé pour recevoir les éléments produits
par la coroutine
31. RxKotlin VS Kotlin coroutines - Kotlin coroutines
fun main(args: Array<String>) {
launch { // launch new coroutine
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main function continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
Hello,
World!
Coroutine builders
32. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Coroutine dispatchers
Des composants qui permettent de gérer l’exécution de la coroutine au niveau Threads, notamment, ils
déterminent quel Thread ou quels Threads , la coroutine utilisera pour son exécution
Module kotlinx-coroutines-core
33. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Coroutine dispatchers
fun main(args: Array<String>) = runBlocking<Unit> {
val jobs = arrayListOf<Job>()
jobs += launch(Unconfined) { // not confined -- will work with main thread
println(" 'Unconfined': I'm working in thread ${Thread.currentThread().name}")
}
jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine
println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}")
}
jobs += launch(CommonPool) { // will get dispatched to ForkJoinPool.commonPool (or equivalent)
println(" 'CommonPool': I'm working in thread ${Thread.currentThread().name}")
}
jobs += launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
println(" 'newSTC': I'm working in thread ${Thread.currentThread().name}")
}
jobs.forEach { it.join() } }
34. RxKotlin VS Kotlin coroutines - Kotlin coroutines
'Unconfined': I'm working in thread main
'CommonPool': I'm working in thread ForkJoinPool.commonPool-worker-1
'newSTC': I'm working in thread MyOwnThread
'coroutineContext': I'm working in thread main
Coroutine dispatchers
35. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Channels
Des composants qui fournissent un moyen de transmettre / transférer des flux de données (à travers les
méthodes send et receive)
fun main(args: Array<String>) = runBlocking<Unit> {
val channel = Channel<Int>()
launch { // this might be heavy CPU-consuming computation or async logic, we'll just send five squares
for (x in 1..5) channel.send(x * x)
}
// here we print five received integers:
repeat(5) { println(channel.receive()) }
println("Done!")
}
1
4
9
16
25
Done!
36. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Concurrence et state partagé
Les coroutines peuvent être exécuté de façon concurrente, en utilisant un dispatcher multiThreadé
(CommonPool). Le problème dans ce cas là est de garder un état partagé et mutable entre les différents coroutines
s’exécutant sur les différents Threads suspend fun massiveRun(context: CoroutineContext, action: suspend () ->
Unit) {
val n = 1000 // number of coroutines to launch
val k = 1000 // times an action is repeated by each coroutine
val time = measureTimeMillis {
val jobs = List(n) {
launch(context) {
repeat(k) { action() }
}
}
jobs.forEach { it.join() }
}
println("Completed ${n * k} actions in $time ms") }
37. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Concurrence et state partagé
var counter = 0
fun main(args: Array<String>) = runBlocking<Unit> {
massiveRun(CommonPool) {
counter++
}
println("Counter = $counter")
}
Thread-safe data structures
AtomicInteger (methods incrementAndGet, …)
Confinement léger des Threads
Tous les accès au state partagé se font à travers un seul
Thread
Confinement fort des Threads
Les modifications par bloc se font uniquement à travers
un seul Thread
Exclusion mutuelle
Mutex (méthodes Lock et Unlock)
Actors
Coroutine + state encapsulé dans la coroutine + channel
utilisé pour communiquer avec les autres coroutines
38. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Expressions select
Donner la possibilité d’attendre plusieurs “suspending functions” de façon simultanée et d’en sélectionner la
première qui devient disponible
fun fizz(context: CoroutineContext) = produce<String>(context) {
while (true) { // sends "Fizz" every 300 ms
delay(300) send("Fizz")
}
}
fun buzz(context: CoroutineContext) = produce<String>(context) {
while (true) { // sends "Buzz!" every 500 ms
delay(500) send("Buzz!")
}
}
39. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Expressions select
suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
select<Unit> { // <Unit> means that this select expression does not produce any result
fizz.onReceive { value -> // this is the first select clause
println("fizz -> '$value'")
}
buzz.onReceive { value -> // this is the second select clause
println("buzz -> '$value'")
}
}
}
42. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Intégration des coroutines à votre projet
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>0.19.2</version>
</dependency>
Maven
<properties> <kotlin.version>1.1.51</kotlin.version> </properties>
Gradle
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.2'
buildscript { ext.kotlin_version = '1.1.51' }
43. RxKotlin VS Kotlin coroutines - Kotlin coroutines
Les différents modules de la librairie Kotlin coroutines
Core
• coroutine builders
• Future légers (possibilité d’arrêt)
• Contextes des coroutines
• Communication et synchronisation (Channles et Mutex)
• Fonctions de suspension de haut niveau
• Expressions Select
Reactive
• Builders et itérations pour les différentes librairies
de flux réactives (Reactive Streams, RxJava 1.x
et 2.x, Reactor)
UI
• Dispatchers pour les différentes librairies UI
(Single-Threaded) : Android, JavaFx, Swing
Intégration
• Intégration avec les différentes librairies
(callbacks asynchrones et Future) :
JDK8 CompletableFuture, Guava ListenableFuture
and synchronous networking/IO