Slide 1

Slide 1 text

Channels & Flows Mohit Sarveiya /heyitsmohit twitter.com

Slide 2

Slide 2 text

Channels & Flows • Channels • Actors • Flows

Slide 3

Slide 3 text

Coroutine A Passing Data Coroutine B

Slide 4

Slide 4 text

Coroutine A Passing Data Coroutine B

Slide 5

Slide 5 text

Passing Data • Deferred • Channels • Flows

Slide 6

Slide 6 text

Channels

Slide 7

Slide 7 text

What is a Channel? • Similar to BlockingQueue.
 interface Channel { suspend fun send(element: E) suspend fun receive(): E }

Slide 8

Slide 8 text

Using Channels runBlocking {
 val channel = Channel() launch { channel.send(1) } val num = channel.receive() }

Slide 9

Slide 9 text

Using Channels runBlocking {
 val channel = Channel() launch { channel.send(1) } val num = channel.receive() } Parent Coroutine

Slide 10

Slide 10 text

Using Channels runBlocking {
 val channel = Channel() launch { channel.send(1) } val num = channel.receive() } Child Coroutine

Slide 11

Slide 11 text

Using Channels runBlocking {
 val channel = Channel() launch { channel.send(1) } val num = channel.receive() }

Slide 12

Slide 12 text

Using Channels runBlocking {
 val channel = Channel() launch { channel.send(1) } val num = channel.receive() }

Slide 13

Slide 13 text

Using Channels runBlocking {
 val channel = Channel() launch { channel.send(1) } val num = channel.receive() }

Slide 14

Slide 14 text

Channel Types • Rendezvous • Buffered • Unlimited • Conflated

Slide 15

Slide 15 text

Creating Channel fun Channel(capacity: Int = RENDEZVOUS) = when (capacity) { RENDEZVOUS !-> RendezvousChannel() UNLIMITED !-> LinkedListChannel() CONFLATED !-> ConflatedChannel() BUFFERED !-> ArrayChannel(CHANNEL_DEFAULT_CAPACITY) else !-> ArrayChannel(capacity) }

Slide 16

Slide 16 text

Rendezvous Channel • No Buffer • Waits for receiver to send data

Slide 17

Slide 17 text

Rendezvous Channel runBlocking { val channel = Channel() launch { channel.send(1) channel.send(2) } }

Slide 18

Slide 18 text

Rendezvous Channel runBlocking { val channel = Channel() launch { channel.send(1) channel.send(2) } } SUSPEND

Slide 19

Slide 19 text

Rendezvous Channel runBlocking { val channel = Channel() launch { channel.send(1) channel.send(2) }
 channel.receive() } Receiver

Slide 20

Slide 20 text

runBlocking { val channel = Channel() launch { channel.send(1)
 channel.send(2) }
 channel.receive() } Rendezvous Channel RESUME

Slide 21

Slide 21 text

runBlocking { val channel = Channel() launch { channel.send(1)
 channel.send(2) }
 channel.receive() } Rendezvous Channel SUSPEND

Slide 22

Slide 22 text

Channel State • Queue • Buffer (Optional)

Slide 23

Slide 23 text

Rendezvous Channel val channel = Channel() EmptyQueue Queue

Slide 24

Slide 24 text

Channel States • Empty Queue • Send Queued • Send Buffered • Closed

Slide 25

Slide 25 text

Channel States EmptyQueue LockFreeLinkedList SendBuffered SendQueued Closed

Slide 26

Slide 26 text

Rendezvous Channel val channel = Channel() channel.send(1)

Slide 27

Slide 27 text

Rendezvous Channel class SendElement( val data: Any?, val cont: CancellableContinuation ) : LockFreeLinkedListNode()

Slide 28

Slide 28 text

Rendezvous Channel val channel = Channel() channel.send(1) SendElement(1) Queue

Slide 29

Slide 29 text

Rendezvous Channel val channel = Channel() channel.send(1)
 channel.receive() EmptyQueue Queue

Slide 30

Slide 30 text

class AbstractSendChannel { fun toString() = {$queueDebugStateString}$bufferDebugString” } Query Channel State Current Channel State Buffer Size

Slide 31

Slide 31 text

Query Channel State • Get most recent state • Size of buffer

Slide 32

Slide 32 text


 
 $queueDebugStateString get() {
 if (head !!=== queue) return "EmptyQueue" } Query Channel State EmptyQueue

Slide 33

Slide 33 text


 
 
 $queueDebugStateString get() {
 when (head) {
 is Closed!!<*> !-> head.toString()
 is Receive!!<*> !-> "ReceiveQueued"
 is Send !-> "SendQueued" else !-> "UNEXPECTED:$head" } } Query Channel State SendElement

Slide 34

Slide 34 text


 
 
 $queueDebugStateString get() {
 if (tail !!!== head) { result += ",queueSize=${countQueueSize()}" } } Query Channel State SendElement Buffer SendElement SendElement

Slide 35

Slide 35 text

Query Channel State val channel = Channel() channel.send(1)
 println(channel)
 
 Output: 
 RendezvousChannel{SendQueued}

Slide 36

Slide 36 text

Idiomatic Channel Creation runBlocking {
 val channel = Channel() launch { for (i in 1!..10) { channel.send(i) } } }

Slide 37

Slide 37 text

Idiomatic Channel Creation runBlocking {
 val channel = Channel() launch { for (i in 1!..10) { channel.send(i) } } } Producer

Slide 38

Slide 38 text

Idiomatic Channel Creation 
 fun CoroutineScope.producer(): ReceiveChannel = 
 produce { for (i in 1!..5) { send(i) } }

Slide 39

Slide 39 text

Idiomatic Channel Creation runBlocking {
 val channel: ReceiveChannel = producer() }

Slide 40

Slide 40 text

Producer Builder • Creates Coroutine with Channel • DSL for creating channels

Slide 41

Slide 41 text

Produce Builder fun CoroutineScope.produce( context: CoroutineContext, capacity: Int = 0, block: suspend ProducerScope.() !-> Unit ): ReceiveChannel

Slide 42

Slide 42 text

Produce Builder fun CoroutineScope.produce(…) { val channel = Channel(capacity) val newContext = newCoroutineContext(context) val coroutine = ProducerCoroutine(ctx, channel) coroutine.start(…, coroutine, block) return coroutine }

Slide 43

Slide 43 text

Idiomatic Channel Consumption runBlocking {
 val channel: ReceiveChannel = producer() channel.close() }

Slide 44

Slide 44 text

Idiomatic Channel Consumption runBlocking {
 val channel: ReceiveChannel = producer() channel.close() for (num in channel) { calculate(num) } }

Slide 45

Slide 45 text

Channel Iterator interface ChannelIterator { suspend fun hasNextSuspend(): E fun next(): E }

Slide 46

Slide 46 text

Coroutine A Channel Iterator Coroutine B 1 2 3 x Closed token

Slide 47

Slide 47 text

Coroutine A Channel Iterator Coroutine B EmptyQueue Channel Queue

Slide 48

Slide 48 text

Coroutine A Channel Iterator Coroutine B 1 SendElement(1) Channel Queue hasNext()

Slide 49

Slide 49 text

Coroutine A Channel Iterator Coroutine B 1 SendElement(1) Channel Queue next()

Slide 50

Slide 50 text

Coroutine A Channel Iterator Coroutine B 1 SendElement(1) Channel Queue next()

Slide 51

Slide 51 text

Coroutine A Channel Iterator Coroutine B 1 SendElement(3) Channel Queue next() 2 3

Slide 52

Slide 52 text

Coroutine A Channel Iterator Coroutine B 1 Closed Channel Queue 2 3 x Closed token

Slide 53

Slide 53 text

Coroutine A Channel Iterator Coroutine B 1 2 3 Suspended No Closed token

Slide 54

Slide 54 text

Channel Iterator fun hasNextSuspend() = suspendCancellable { cont !-> while (true) { val result = channel.pollInternal() this.result = result if (result is Closed!!<*>) {
 cont.resume(false) } else { cont.resume(true) } …
 } }

Slide 55

Slide 55 text

Channel Types • Rendezvous • Buffered • Unlimited • Conflated

Slide 56

Slide 56 text

Buffered Channel • Buffer with specific capacity. • Caches sent items in Array

Slide 57

Slide 57 text

Buffered Channel runBlocking { val channel = Channel(capacity = 3) }

Slide 58

Slide 58 text

Buffered Channel class ArrayChannel(val capacity: Int) { val buffer = arrayOfNulls(capacity) … }

Slide 59

Slide 59 text

runBlocking { val channel = Channel(capacity = 3) launch { channel.send(1) channel.send(2) channel.send(3) } } Buffered Channel 1 2 3 Buffer

Slide 60

Slide 60 text

runBlocking { val channel = Channel(capacity = 3) launch { channel.send(1) channel.send(2) channel.send(3)
 channel.send(4) } } Buffered Channel SUSPEND 1 2 3 Buffer

Slide 61

Slide 61 text

Unlimited Channel • Buffer with unlimited capacity. • LinkedList Buffer.

Slide 62

Slide 62 text

Unlimited Channel runBlocking { val channel = Channel(Channel.UNLIMITED) } Empty Queue

Slide 63

Slide 63 text

Unlimited Channel runBlocking { val channel = Channel(Channel.UNLIMITED) launch { channel.send(1) channel.send(2) channel.send(3) } } SendBuffered(1) SendBuffered(2) SendBuffered(3)

Slide 64

Slide 64 text

Conflated Channel • Get most recent sent element • Similar to ReplaySubject (RxJava)

Slide 65

Slide 65 text

Conflated Channel 
 val channel = Channel(Channel.CONFLATED) channel.send(1) channel.send(2) !// Conflate previously sent 
 println(channel.receive()) Output:
 2

Slide 66

Slide 66 text

Conflated Channel 
 val channel = Channel(Channel.CONFLATED) channel.send(1) SendBuffered(1)

Slide 67

Slide 67 text

Conflated Channel 
 val channel = Channel(Channel.CONFLATED) channel.send(1) channel.send(2) SendBuffered(2)

Slide 68

Slide 68 text

Conflated Channel 
 val channel = Channel(Channel.CONFLATED) channel.send(1) channel.send(2) SendBuffered(2) Never Suspend

Slide 69

Slide 69 text

Channel Types • Rendezvous • Buffered • Unlimited • Conflated

Slide 70

Slide 70 text

Channels Rendezvous • Queue size = 1 • Buffer: None Array Channel • Queue size = 1 • Buffer: Array Linked List Channel • Queue size = Unlimited • Buffer: Queue Conflated • Queue size = 1 • Buffer: Queue

Slide 71

Slide 71 text

Tips for using Channels • Query channel queue for debugging. • Channel type determines back pressure strategy to use.

Slide 72

Slide 72 text

Actors

Slide 73

Slide 73 text

Problems they solve • Shared mutable state • Event conflation

Slide 74

Slide 74 text

Problem class MyViewModel(val interactor: Interactor) { val scope = CoroutineScope(Dispatchers.Main) fun refresh() { scope.launch { val response = interactor.getData() handleResponse(response) } } }

Slide 75

Slide 75 text

Problem class MyViewModel(val interactor: Interactor) { val scope = CoroutineScope(Dispatchers.Main) fun refresh() { scope.launch { val response = interactor.getData() handleResponse(response) } } } Ensure 1 concurrent job runs

Slide 76

Slide 76 text

Solution • Check status of Job • Use Actors

Slide 77

Slide 77 text

Check Job State class MyViewModel(val interactor: Interactor) { val job: Job? = null fun refresh() { if (job!?.isCompleted !== false) return
 val job = scope.launch { val response = interactor.getData() handleResponse(response) } } }

Slide 78

Slide 78 text

Actor State Actor Channel • Coroutine with mailbox (Channel)

Slide 79

Slide 79 text

Actor State Actor Channel Coroutine Event

Slide 80

Slide 80 text

Actor State Actor Channel Coroutine Event

Slide 81

Slide 81 text

Events Channel Coroutine LoadData sealed class ActorEvents object LoadData : ActorEvents() …

Slide 82

Slide 82 text

fun CoroutineScope.actor( capacity: Int = 0, block: suspend ActorScope.() !-> Unit ): SendChannel { val channel = Channel(capacity) val coroutine = ActorCoroutine(ctx, channel, true) coroutine.start(start, coroutine, block) return coroutine } Inside Actor

Slide 83

Slide 83 text

fun CoroutineScope.modelActor() = 
 
 actor(capacity = Channel.CONFLATED) { for (event in channel) { when(event) { is ActorEvents.LoadData !-> apiResponse = interactor.getData() }
 } } Using Actor

Slide 84

Slide 84 text

Actor Use Cases • UI Event Conflation • Implement Redux-like architecture

Slide 85

Slide 85 text

Flows

Slide 86

Slide 86 text

What is a Flow? • Cold stream interface Flow { suspend fun collect(collector: FlowCollector) }

Slide 87

Slide 87 text

What is a Flow? interface Flow { suspend fun collect(collector: FlowCollector) } Consume stream

Slide 88

Slide 88 text

What is a Flow? interface Flow { suspend fun collect(collector: FlowCollector) } What to consume?

Slide 89

Slide 89 text

What is a Flow? interface FlowCollector { suspend fun emit(value: T) }

Slide 90

Slide 90 text

Using Flows runBlocking { val flow: Flow = flowOf(1, 2, 3) flow.collect { println(it) } }

Slide 91

Slide 91 text

Creating Flows • flowOf(…) • flow { … }

Slide 92

Slide 92 text

flowOf Builder flowOf(1, 2, 3) fun flowOf(vararg elements: T): Flow

Slide 93

Slide 93 text

flowOf Builder fun flowOf(vararg elements: T): Flow { object : Flow { override suspend fun collect(collector: FlowCollector) { for (element in elements) { collector.emit(element) } } } }

Slide 94

Slide 94 text

Flow Builder runBlocking { flow { emit(1) emit(2) emit(3) } }

Slide 95

Slide 95 text

Flow Builder fun flow( block: suspend FlowCollector.() !-> Unit ): Flow

Slide 96

Slide 96 text

Terminal Operators • Collect • Single • Reduce • toList

Slide 97

Slide 97 text

Single Operator Collects only one value. val flow = flowOf(1) val num: Int = flow.single()

Slide 98

Slide 98 text

Single Operator Collects only one value. val flow = flowOf(1,2,3) val num: Int = flow.single() java.lang.IllegalStateException: Expected only one element

Slide 99

Slide 99 text

Flow Operators • Map • Filter • Take • Zip • delayEach • delayFlow

Slide 100

Slide 100 text

DelayEach val flow = flowOf(1,2,3) flow.delayEach(1000) .collect { println(it) }

Slide 101

Slide 101 text

DelayEach val flow = flowOf(1,2,3) flow.delayEach(1000) .collect { println(it) } Operators are not suspending functions

Slide 102

Slide 102 text

DelayEach runBlocking { val flow = getFlow() flow.collect { println(it) } } fun getFlow() = flowOf(1,2,3).delayEach(1000)

Slide 103

Slide 103 text

Flow Constraints • Context Preservation • Exception Transparency

Slide 104

Slide 104 text

Flow Constraints Emit from the same coroutine val flow = flow { launch(Dispatchers.IO) { emit(1) } } Flow invariant is violated: emission from another coroutine is detected

Slide 105

Slide 105 text

Channel Flow Builder val flow = channelFlow { launch(Dispatchers.IO) { send(1) } launch(Dispatchers.IO) { send(2) } } Flow Channel

Slide 106

Slide 106 text

Channel Flow Builder class ChannelFlowBuilder( val block: suspend ProducerScope.() !-> Unit, capacity: Int = BUFFERED ) : ChannelFlow(context, capacity) Uses Buffered Channel

Slide 107

Slide 107 text

Flow Context runBlocking { val flow = flow { emit(1) emit(2) emit(3) } flow .map { it + 1 } .collect { println(it) } }

Slide 108

Slide 108 text

Flow Context runBlocking { val flow = flow { emit(1) emit(2) emit(3) } flow .map { it + 1 } .collect { println(it) } } Dispatchers.Main Dispatchers.Main

Slide 109

Slide 109 text

Flow Context runBlocking { val flow = flow { emit(1) emit(2) emit(3) } flow .map { it + 1 } .flowOn(Dispatchers.IO) .collect { println(it) } } Dispatchers.IO Dispatchers.IO Dispatchers.Main

Slide 110

Slide 110 text

Exception Transparency val flow = flow { try { makeRequest() } catch (e: IOException) { } }

Slide 111

Slide 111 text

Exception Transparency fun Flow.catch(action: suspend FlowCollector.(cause: Throwable) !-> Unit ): Flow Emit exception in a wrapper

Slide 112

Slide 112 text

Exception Transparency sealed class ApiResponse { data class Success(val data: T): ApiResponse() data class Error(val t: Throwable): ApiResponse() }

Slide 113

Slide 113 text

Exception Transparency val flow = flow> { val data = makeRequest() emit(ApiResponse.Success(data)) }.catch { emit(ApiResponse.Error(it)) }

Slide 114

Slide 114 text

Resources

Slide 115

Slide 115 text

Debugging (JVM) runBlocking { DebugProbes.install() val channel = Channel() launch { channel.send(2) }
 DebugProbes.dumpCoroutines() } Coroutine StandaloneCoroutine{Active}, state: SUSPENDED Install Debugger View Coroutines State

Slide 116

Slide 116 text

Resources (Inside Channels)

Slide 117

Slide 117 text

Thank you! twitter.com/heyitsmohit