Kotlin Coroutines in Practice Roman Eliza zarov !"#$%&( - - PowerPoint PPT Presentation

kotlin coroutines in practice
SMART_READER_LITE
LIVE PREVIEW

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)


slide-1
SLIDE 1

!"#$%&’( ) Roman Eliza zarov

Kotlin Coroutines in Practice

)&!"#$%&’(

slide-2
SLIDE 2

Coroutines recap

slide-3
SLIDE 3
slide-4
SLIDE 4

suspend fun main() { val jobs = List(100_000) { GlobalScope.launch { delay(5000) print(".") } } jobs.forEach { it.join() } }

Coroutines are like light-weight threads

slide-5
SLIDE 5

suspend f un main() { val jobs = List(100_000) { GlobalScope.launch { delay(5000) print(". ") } } jobs.forEach { i t .join() } }

Coroutines are like light-weight threads

slide-6
SLIDE 6

suspend fun main() { val jobs = List(100_000) { GlobalScope.launch { delay(5000) print(".") } } jobs.forEach { it.join() } }

Coroutines are like light-weight threads

slide-7
SLIDE 7

Quantity

slide-8
SLIDE 8

Quantity → Quality

slide-9
SLIDE 9

A practical challenge

suspend fun downloadContent(location: Location): Content

slide-10
SLIDE 10

fun processReferences(refs: List<Reference>)

References

slide-11
SLIDE 11

References

f un processReferences(refs: List<Reference>) { f or (ref i n refs) { … } }

slide-12
SLIDE 12

References Locations

resolve

fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() … } }

slide-13
SLIDE 13

References Locations Contents

resolve download

fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }

slide-14
SLIDE 14

References Locations Contents

resolve download

suspend fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }

slide-15
SLIDE 15

References Locations Contents

resolve download

suspend fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }

Sequential

slide-16
SLIDE 16

References Locations Contents

resolve download

fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() val content = downloadContent(location) processContent(ref, content) } }

Parallel

slide-17
SLIDE 17

References Locations Contents

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

slide-18
SLIDE 18

fun processReferences(refs: List<Reference>) { for (ref in refs) { val location = ref.resolveLocation() GlobalScope.launch { val content = downloadContent(location) processContent(ref, content) } } }

Coroutines are cheap! What could go wrong?

slide-19
SLIDE 19

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!

slide-20
SLIDE 20

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) } } }

slide-21
SLIDE 21

Structured concurrency

slide-22
SLIDE 22

fun processReferences(refs: List<Reference>)

slide-23
SLIDE 23

suspend fun processReferences(refs: List<Reference>)

slide-24
SLIDE 24

suspend fun processReferences(refs: List<Reference>) = coroutineScope { … }

slide-25
SLIDE 25

suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() GlobalScope.launch { val content = downloadContent(location) processContent(ref, content) } } }

slide-26
SLIDE 26

suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }

slide-27
SLIDE 27

suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }

Child

slide-28
SLIDE 28

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 ) } } }

slide-29
SLIDE 29

suspend fun processReferences(refs: List<Reference>) = coroutineScope { for (ref in refs) { val location = ref.resolveLocation() launch { val content = downloadContent(location) processContent(ref, content) } } }

Crash?

slide-30
SLIDE 30

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

slide-31
SLIDE 31

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) } } }

slide-32
SLIDE 32

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

slide-33
SLIDE 33

The state

slide-34
SLIDE 34

References Contents

Download process

slide-35
SLIDE 35

Reference 1 Content Reference 2 Location

Download process

State

slide-36
SLIDE 36

class Downloader { }

slide-37
SLIDE 37

class Downloader { private val requested = mutableSetOf<Location>() }

slide-38
SLIDE 38

class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() … } }

slide-39
SLIDE 39

class Downloader { private val requested = mutableSetOf<Location>() fun downloadReference(ref: Reference) { val location = ref.resolveLocation() if (requested.add(location)) { // schedule download } } }

slide-40
SLIDE 40

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) } }

slide-41
SLIDE 41

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

slide-42
SLIDE 42

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

slide-43
SLIDE 43

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

slide-44
SLIDE 44

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

Shared + Mutable =

slide-45
SLIDE 45

Shared Mutable State Share by Communicating

slide-46
SLIDE 46

Synchronization Primitives Communication Primitives

slide-47
SLIDE 47

classes coroutines

slide-48
SLIDE 48

launch { val r equest ed = mutableSetOf<Locat i on>( ) … }

Does not share mutable state

slide-49
SLIDE 49

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) } }

slide-50
SLIDE 50

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) } }

slide-51
SLIDE 51

Channel

References Downloader

slide-52
SLIDE 52

fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }

slide-53
SLIDE 53

fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }

slide-54
SLIDE 54

fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }

slide-55
SLIDE 55

fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, ) = launch { … }

Convention

slide-56
SLIDE 56

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 ) } }

slide-57
SLIDE 57

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) } }

slide-58
SLIDE 58

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) } }

slide-59
SLIDE 59

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 ) } }

slide-60
SLIDE 60

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) } }

Coroutines are cheap! What could go wrong?

slide-61
SLIDE 61

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

slide-62
SLIDE 62

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) } }

Coroutines are cheap! But the work they do…

slide-63
SLIDE 63

Limiting concurrency

slide-64
SLIDE 64

Worker 1 Worker 2 Worker 3 Worker N Worker pool

References Downloader references locations

slide-65
SLIDE 65

fun Cor out i neScope. downl oader ( r ef er ences: Recei veChannel <Ref er ence>, l ocat i ons: SendChannel <Locat i on> )

slide-66
SLIDE 66

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) } } }

slide-67
SLIDE 67

fun CoroutineScope.worker( locations: ReceiveChannel<Location> )

slide-68
SLIDE 68

fun CoroutineScope.worker( locations: ReceiveChannel<Location> )

slide-69
SLIDE 69

fun CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { for (loc in locations) { val content = downloadContent(loc) processContent(ref, content) } }

slide-70
SLIDE 70

f un CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { f or (loc i n locations) { val content = downloadContent(loc) processContent(ref, content) } }

Fan-out

slide-71
SLIDE 71

fun CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { for (loc in locations) { val content = downloadContent(location) processContent(ref, content) } }

slide-72
SLIDE 72

fun CoroutineScope.worker( locations: ReceiveChannel<Location> ) = launch { for (loc in locations) { val content = downloadContent(loc) processContent(ref, content) } }

slide-73
SLIDE 73

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 ) } }

slide-74
SLIDE 74

Worker 1 Worker 2 Worker 3 Worker N

References Downloader

slide-75
SLIDE 75

Worker 1 Worker 2 Worker 3 Worker N

Ref er ences Downl oader Ref s ↔ Locs l ocat i on & cont ent

slide-76
SLIDE 76

data class LocContent(val loc: Location, val content: Content)

slide-77
SLIDE 77

data class LocContent(val loc: Location, val content: Content) fun CoroutineScope.worker( locations: ReceiveChannel<Location>, contents: SendChannel<LocContent> )

slide-78
SLIDE 78

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)) } }

slide-79
SLIDE 79

Worker 1 Worker 2 Worker 3 Worker N

References Downloader locations contents

slide-80
SLIDE 80

fun CoroutineScope.downloader( references: ReceiveChannel<Reference>, locations: SendChannel<Location>, contents: ReceiveChannel<LocContent> )

slide-81
SLIDE 81

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) } } }

Hmm….

slide-82
SLIDE 82

Select

slide-83
SLIDE 83

sel ect { r ef er ences. onRecei ve { r ef - > … } cont ent s. onRecei ve { ( l oc, cont ent ) - > … } }

slide-84
SLIDE 84

select { references.onReceive { ref -> … } contents.onReceive { (loc, content) -> … } }

slide-85
SLIDE 85

select<Unit> { references.onReceive { ref -> … } contents.onReceive { (loc, content) -> … } }

slide-86
SLIDE 86

launch { val requested = mutableMapOf<Location, MutableList<Reference>>() … }

slide-87
SLIDE 87

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 ) -> … } } } }

slide-88
SLIDE 88

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 ) -> … } } } }

slide-89
SLIDE 89

launch { val requested = mutableMapOf<Location, MutableList<Reference>>() while (true) { select<Unit> { references.onReceive { ref -> val loc = ref.resolveLocation() … } contents.onReceive { (loc, content) -> … } } } }

slide-90
SLIDE 90

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) -> … } } } }

slide-91
SLIDE 91

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) -> … } } } }

slide-92
SLIDE 92

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) -> … } } } }

slide-93
SLIDE 93

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

slide-94
SLIDE 94

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

  • ve( l oc) ! !

for ( r ef in r ef s) { processContent( r ef , cont ent ) } } } } }

slide-95
SLIDE 95

Putting it all together

slide-96
SLIDE 96

Worker 1 Worker 2 Worker 3 Worker N

References Downloader locations contents references

slide-97
SLIDE 97

Worker 1 Worker 2 Worker 3 Worker N

References Downloader locations contents references

slide-98
SLIDE 98

fun CoroutineScope.processReferences( references: ReceiveChannel<Reference> )

slide-99
SLIDE 99

fun Cor out i neScope. pr ocessRef er ences( r ef er ences: Recei veChannel <Ref er ence> )

slide-100
SLIDE 100

fun CoroutineScope.processReferences( references: ReceiveChannel<Reference> ) { val locations = Channel<Location>() val contents = Channel<LocContent>() repeat(N_WORKERS) { worker(locations, contents) } downloader(references, locations, contents) }

slide-101
SLIDE 101

fun CoroutineScope.processReferences( references: ReceiveChannel<Reference> ) { val locations = Channel<Location>() val contents = Channel<LocContent>() repeat(N_WORKERS) { worker(locations, contents) } downloader(references, locations, contents) }

slide-102
SLIDE 102

Worker 1 Worker 2 Worker 3 Worker N

References Downloader locations contents references

slide-103
SLIDE 103

Worker 1 Worker 2 Worker 3 Worker N

References Downloader locations contents references processReferences

slide-104
SLIDE 104

Worker 1 Worker 2 Worker 3 Worker N

slide-105
SLIDE 105

fun CoroutineScope.processReferences(…)

slide-106
SLIDE 106

Root CoroutineScope

slide-107
SLIDE 107

class Som et hi ngW i t hLi f ecycl e { }

slide-108
SLIDE 108

class SomethingWithLifecycle : CoroutineScope { }

slide-109
SLIDE 109

class SomethingWithLifecycle : CoroutineScope {

  • verride val coroutineContext: CoroutineContext

get() = … }

slide-110
SLIDE 110

class SomethingWithLifecycle : CoroutineScope { private val job = Job()

  • verride val coroutineContext: CoroutineContext

get() = … }

slide-111
SLIDE 111

class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun dispose() { … }

  • verride val coroutineContext: CoroutineContext

get() = … }

slide-112
SLIDE 112

class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun close() { … }

  • verride val coroutineContext: CoroutineContext

get() = … }

slide-113
SLIDE 113

class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun close() { job.cancel() }

  • verride val coroutineContext: CoroutineContext

get() = … }

slide-114
SLIDE 114

class SomethingWithLifecycle : CoroutineScope { private val job = Job() fun close() { job.cancel() }

  • verride val coroutineContext: CoroutineContext

get() = job }

slide-115
SLIDE 115

class Som et hi ngW i t hLi f ecycl e : Cor out i neScope { private val job = Job( ) fun cl ose( ) {

  • job. cancel ( )

}

  • verride val coroutineContext: Cor out i neCont ext

get( ) = job + Di spat cher s. Main }

slide-116
SLIDE 116

class SomethingWithLifecycle : CoroutineScope { …

  • verride val coroutineContext: CoroutineContext

get() = job + Dispatchers.Main fun doSomething() { } }

slide-117
SLIDE 117

cl ass SomethingWithLifecycle : CoroutineScope { …

  • ver r i de val

cor out i neCont ext : CoroutineContext get () = j ob + Dispatchers.M ai n f un doSomething() { l aunch { … } } }

slide-118
SLIDE 118

class SomethingWithLifecycle : CoroutineScope { …

  • verride val coroutineContext: CoroutineContext

get() = job + Dispatchers.Main fun doSomething() { processReferences(references) } }

Never leak any coroutines

slide-119
SLIDE 119

suspend vs scope

slide-120
SLIDE 120

suspend fun downl oadCont ent ( l ocat i on: Locat i on) : Cont ent

slide-121
SLIDE 121

suspend fun downloadContent(location: Location): Content

Does something long & waits for it to complete without

slide-122
SLIDE 122

suspend fun downloadContent(location: Location): Content

fun CoroutineScope.processReferences(…)

slide-123
SLIDE 123

suspend fun downloadContent(location: Location): Content

fun CoroutineScope.processReferences(…)

Launches new coroutines & quickly returns, does not wait for them

slide-124
SLIDE 124

Takeaway

slide-125
SLIDE 125

Coroutines are like light-weight threads

slide-126
SLIDE 126

Coroutines are NO

NOT like

threads

slide-127
SLIDE 127

Coroutines are NO

NOT like

threads

Rethink the way you structure your code

slide-128
SLIDE 128

Thank k yo you

Any y quest stions? s?

elizarov@ Roman Eliza zarov @relizarov