!"#$%&’( ) Roman Eliza zarov
Kotlin Coroutines in Practice
)&!"#$%&’(
Kotlin Coroutines in Practice Roman Eliza zarov !"#$%&( - - PowerPoint PPT Presentation
Kotlin Coroutines in Practice Roman Eliza zarov !"#$%&( ) )&!"#$%&( Coroutines recap Coroutines are like light-weight threads suspend fun main() { val jobs = List (100_000) { GlobalScope. launch { delay (5000)
!"#$%&’( ) Roman Eliza zarov
)&!"#$%&’(
suspend fun main() { val jobs = List(100_000) { GlobalScope.launch { delay(5000) print(".") } } jobs.forEach { it.join() } }
suspend f un main() { val jobs = List(100_000) { GlobalScope.launch { delay(5000) print(". ") } } jobs.forEach { i t .join() } }
suspend fun main() { val jobs = List(100_000) { GlobalScope.launch { delay(5000) print(".") } } jobs.forEach { it.join() } }
suspend fun downloadContent(location: Location): Content
fun processReferences(refs: List<Reference>)
f un processReferences(refs: List<Reference>) { f or (ref i n refs) { … } }
resolve
fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() … } }
resolve download
fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }
resolve download
suspend fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }
resolve download
suspend fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }
Sequential
resolve download
fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }
Parallel
resolve download
fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() GlobalScope.launch { val content = downloadContent(location) processContent(ref, content) } } }
Parallel
fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() GlobalScope.launch { val content = downloadContent(location) processContent(ref, content) } } }
fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() GlobalScope.launch { val content = downloadContent(location) processContent(ref, content) } } }
ref1 location1 Launch download ref2 location2 Launch download ref3 Crash! Crash!
ref1 location1 Launch download ref2 location2 Launch download ref3 Crash! Crash! Leak!
fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() GlobalScope.launch { val content = downloadContent(location) processContent(ref, content) } } }
fun processReferences(refs: List<Reference>)
suspend fun processReferences(refs: List<Reference>)
suspend fun processReferences(refs: List<Reference>) = coroutineScope { … }
suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() GlobalScope.launch { val content = downloadContent(location) processContent(ref, content) } } }
suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }
suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }
Child
Crash?
suspend fun pr ocessRef er ences( r ef s: Li st <Ref er ence>) = cor out i neScope { for ( r ef in r ef s) { val l ocat i on = r ef . r esol veLocat i on( ) l aunch { val cont ent = downl oadCont ent ( l ocat i on) pr ocessCont ent ( r ef , cont ent ) } } }
suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }
Crash?
suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }
Crash? cancels
Crash? cancels Waits for completion
suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }
suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }
Never leaks jobs
References Contents
Reference 1 Content Reference 2 Location
class Downloader { }
class Downloader { private val requested = mutableSetOf<Location>() }
class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() … } }
class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } } }
class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
Concurrent
class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
Concurrent
class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
Shared mutable state
class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
Shared mutable state Needs Syn ynchroniza zation
launch { val r equest ed = mutableSetOf<Locat i on>( ) … }
Does not share mutable state
launch { val requested = mutableSetOf<Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
launch { val requested = mutableSetOf<Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
References Downloader
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }
fun Cor out i neScope. downl oader ( r ef er ences: Recei veChannel <Ref er ence>, ) = launch { val r equest ed = mutableSetOf<Locat i on>( ) for ( r ef in r ef er ences) { val l ocat i on = r ef . r esol veLocat i on( ) if ( r equest ed. add( l ocat i on) ) { // schedule download } // ... wait for result ... processContent( r ef , cont ent ) } }
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { val requested = mutableSetOf<Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { val requested = mutableSetOf<Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } // ... wait for result ... processContent(ref, content) } }
f un Cor out i neScope. downl oader ( r ef er ences: Recei veChannel <Ref er ence>, ) = launch { val r equest ed = mutableSetOf<Locat i on>( ) f or ( r ef i n r ef er ences) { val l ocat i on = r ef . r esol veLocat i on( ) i f ( r equest ed. add( l ocat i on) ) { // schedule download } // ... wait for result ... processContent( r ef , cont ent ) } }
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { val requested = mutableSetOf<Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { launch { … } } // ... wait for result ... processContent(ref, content) } }
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { val requested = mutableSetOf<Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { launch { … } } // ... wait for result ... processContent(ref, content) } }
Child
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = l aunch { val requested = m ut abl eSet O f <Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { l aunch { … } } / / . . . wai t f or r esul t . . . pr ocessCont ent (ref, content) } }
References Downloader references locations
fun Cor out i neScope. downl oader ( r ef er ences: Recei veChannel <Ref er ence>, l ocat i ons: SendChannel <Locat i on> )
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, locations: SendChannel<Location> ) = launch { val requested = mutableSetOf<Location>() for (ref in references) { val location = ref.resolveLocation() if (requested.add(location)) { locations.send(location) } } }
fun CoroutineScope.worker( locations: ReceiveChannel<Location> )
fun CoroutineScope.worker( locations: ReceiveChannel<Location> )
fun CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { for (loc in locations) { val content = downloadContent(loc) processContent(ref, content) } }
f un CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { f or (loc i n locations) { val content = downloadContent(loc) processContent(ref, content) } }
fun CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { for (loc in locations) { val content = downloadContent(location) processContent(ref, content) } }
fun CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { for (loc in locations) { val content = downloadContent(loc) processContent(ref, content) } }
fun Cor out i neScope. wor ker ( l ocat i ons: Recei veChannel <Locat i on> ) = launch { for ( l oc in l ocat i ons) { val cont ent = downloadContent( l oc) processContent( r ef , cont ent ) } }
References Downloader
Ref er ences Downl oader Ref s ↔ Locs l ocat i on & cont ent
data class LocContent(val loc: Location, val content: Content)
data class LocContent(val loc: Location, val content: Content) fun CoroutineScope.worker( locations: ReceiveChannel<Location>, contents: SendChannel<LocContent> )
data class LocContent(val loc: Location, val content: Content) fun CoroutineScope.worker( locations: ReceiveChannel<Location>, contents: SendChannel<LocContent> ) = launch { for (loc in locations) { val content = downloadContent(loc) contents.send(LocContent(loc, content)) } }
References Downloader locations contents
fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, locations: SendChannel<Location>, contents: ReceiveChannel<LocContent> )
fun Cor out i neScope. downl oader ( r ef er ences: Recei veChannel <Ref er ence>, l ocat i ons: SendChannel <Locat i on>, cont ent s: Recei veChannel <LocCont ent > ) = launch { val r equest ed = mutableSetOf<Locat i on>( ) for ( r ef in r ef er ences) { val l ocat i on = r ef . r esol veLocat i on( ) if ( r equest ed. add( l ocat i on) ) { l ocat i ons. send( l ocat i on) } } }
sel ect { r ef er ences. onRecei ve { r ef - > … } cont ent s. onRecei ve { ( l oc, cont ent ) - > … } }
select { references.onReceive { ref -> … } contents.onReceive { (loc, content) -> … } }
select<Unit> { references.onReceive { ref -> … } contents.onReceive { (loc, content) -> … } }
launch { val requested = mutableMapOf<Location, MutableList<Reference>>() … }
launch { val r equest ed = mutableMapOf<Locat i on, M ut abl eLi st <Ref er ence>>( ) while ( true) { sel ect <Uni t > { r ef er ences. onReceive { r ef -> … } cont ent s. onReceive { ( l oc, cont ent ) -> … } } } }
launch { val r equest ed = mutableMapOf<Locat i on, M ut abl eLi st <Ref er ence>>( ) while ( true) { sel ect <Uni t > { r ef er ences. onReceive { r ef -> … } cont ent s. onReceive { ( l oc, cont ent ) -> … } } } }
launch { val requested = mutableMapOf<Location, MutableList<Reference>>() while (true) { select<Unit> { references.onReceive { ref -> val loc = ref.resolveLocation() … } contents.onReceive { (loc, content) -> … } } } }
launch { val requested = mutableMapOf<Location, MutableList<Reference>>() while (true) { select<Unit> { references.onReceive { ref -> val loc = ref.resolveLocation() val refs = requested[loc] … } contents.onReceive { (loc, content) -> … } } } }
launch { val requested = mutableMapOf<Location, MutableList<Reference>>() while (true) { select<Unit> { references.onReceive { ref -> val loc = ref.resolveLocation() val refs = requested[loc] if (refs == null) { requested[loc] = mutableListOf(ref) locations.send(loc) } } contents.onReceive { (loc, content) -> … } } } }
l aunch { val requested = m ut abl eM apO f <Location, MutableList<Reference>>() while (true) { select<Unit> { references.onReceive { ref -> val loc = ref.resolveLocation() val refs = requested[loc] if (refs != null) { requested[loc] = m ut abl eLi st O f (ref) locations.send(loc) } else { refs.add(ref) } } contents.onReceive { (loc, content) -> … } } } }
launch { val requested = mutableMapOf<Location, MutableList<Reference>>() while (true) { select<Unit> { references.onReceive { ref -> val loc = ref.resolveLocation() val refs = requested[loc] if (refs != null) { requested[loc] = mutableListOf(ref) locations.send(loc) } else { refs.add(ref) } } contents.onReceive { (loc, content) -> … } } } }
No concurrency No synchronization
launch { val r equest ed = mutableMapOf<Locat i on, M ut abl eLi st <Ref er ence>>( ) while ( true) { sel ect <Uni t > { r ef er ences. onReceive { r ef -> … } cont ent s. onReceive { ( l oc, cont ent ) -> val r ef s = r equest ed. r em
for ( r ef in r ef s) { processContent( r ef , cont ent ) } } } } }
References Downloader locations contents references
References Downloader locations contents references
fun CoroutineScope.processReferences( references: ReceiveChannel<Reference> )
fun Cor out i neScope. pr ocessRef er ences( r ef er ences: Recei veChannel <Ref er ence> )
fun CoroutineScope.processReferences( references: ReceiveChannel<Reference> ) { val locations = Channel<Location>() val contents = Channel<LocContent>() repeat(N_WORKERS) { worker(locations, contents) } downloader(references, locations, contents) }
fun CoroutineScope.processReferences( references: ReceiveChannel<Reference> ) { val locations = Channel<Location>() val contents = Channel<LocContent>() repeat(N_WORKERS) { worker(locations, contents) } downloader(references, locations, contents) }
References Downloader locations contents references
References Downloader locations contents references processReferences
fun CoroutineScope.processReferences(…)
class Som et hi ngW i t hLi f ecycl e { }
class SomethingWithLifecycle : CoroutineScope { }
class SomethingWithLifecycle : CoroutineScope {
get() = … }
class SomethingWithLifecycle : CoroutineScope { private val job = Job()
get() = … }
class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun dispose() { … }
get() = … }
class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun close() { … }
get() = … }
class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun close() { job.cancel() }
get() = … }
class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun close() { job.cancel() }
get() = job }
class Som et hi ngW i t hLi f ecycl e : Cor out i neScope { private val job = Job( ) fun cl ose( ) {
}
get( ) = job + Di spat cher s. Main }
class SomethingWithLifecycle : CoroutineScope { …
get() = job + Dispatchers.Main fun doSomething() { } }
cl ass SomethingWithLifecycle : CoroutineScope { …
cor out i neCont ext : CoroutineContext get () = j ob + Dispatchers.M ai n f un doSomething() { l aunch { … } } }
class SomethingWithLifecycle : CoroutineScope { …
get() = job + Dispatchers.Main fun doSomething() { processReferences(references) } }
Never leak any coroutines
suspend fun downl oadCont ent ( l ocat i on: Locat i on) : Cont ent
suspend fun downloadContent(location: Location): Content
suspend fun downloadContent(location: Location): Content
fun CoroutineScope.processReferences(…)
suspend fun downloadContent(location: Location): Content
fun CoroutineScope.processReferences(…)
elizarov@ Roman Eliza zarov @relizarov