Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dissecting Coroutines Library

Mohit S
November 28, 2020

Dissecting Coroutines Library

Mohit S

November 28, 2020
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. interface CoroutineContext { operator fun get(key: Key<E>): E? operator fun

    plus(context: CoroutineContext): CoroutineContext }
  2. val job = Job() val scope = CoroutineScope( CoroutineName("My Coroutine")

    + job + Dispatchers.IO ) scope.coroutineContext[Job] / / JobImpl{Active}
  3. Custom Context Use Cases • Dispatcher Provider (Dispatch) • Thread

    Local (krotoPlus) • Database Transactions (Room)
  4. RBusarow/Dispatch Code ! Issues Pull Requests Dispatch Bazel Build passing

    Gradle Build passing Utilities for kotlinx.coroutines which make them type-safe, easier to test, and more expressive.
  5. class DefaultDispatcherProvider { val default: CoroutineDispatcher = Dispatchers.Default val io:

    CoroutineDispatcher = Dispatchers.IO val main: CoroutineDispatcher = Dispatchers.Main }
  6. interface DispatcherProvider : CoroutineContext.Element { override val key: CoroutineContext.Key <

    * > get() = Key companion object Key : CoroutineContext.Key<DispatcherProvider> }
  7. fun <E> Channel(capacity: Int = RENDEZVOUS) = when (capacity) {

    RENDEZVOUS - > RendezvousChannel() UNLIMITED - > LinkedListChannel() CONFLATED - > ConflatedChannel() BUFFERED - > ArrayChannel(CHANNEL_DEFAULT_CAPACITY) else - > ArrayChannel(capacity) }
  8. $queueDebugStateString get() { 
 when (head) { 
 is Closed

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

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

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

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

    < * > - > head.toString() 
 is Receive < * > - > "ReceiveQueued" 
 is Send - > "SendQueued" else - > "UNEXPECTED:$head" } } Query Channel State
  13. runBlocking { 
 val channel = Channel<Int>(10) launch { channel.send(1)

    channel.send(2) channel.send(3) println(channel) } }
  14. runBlocking { 
 val channel = Channel<Int>(10) launch { channel.send(1)

    channel.send(2) channel.send(3) println(channel) } } / / BufferedChannel{SendQueued}{3}
  15. marcoferrer/kroto-plus Code ! Issues Pull Requests Kroto-Plus+ - An RPC

    library and framework Bazel Build passing Gradle Build passing A Kotlin/JVM implementation of gRPC: A high performance, open source, general RPC framework that puts mobile and HTTP/2 first.
  16. Resources • Lock-free algorithms for Kotln Corotuines (Part 1) •

    Lock-free algorithms for Kotln Corotuines (Part 2) • gRPC with Kotlin Coroutines
  17. apollographql/apollo-android Code ! Issues Pull Requests Apollo Android Bazel Build

    passing Gradle Build passing Apollo Android is a GraphQL client that generates Java and Kotlin models from GraphQL queries.
  18. client.query(query).enqueue( object : ApolloCall.Callback() { override fun onResponse(response: Response) {

    } override fun onFailure(e: ApolloException) { } override fun onStatusEvent(event: ApolloCall.StatusEvent) { } })
  19. client.query(query).enqueue( object : ApolloCall.Callback() { override fun onResponse(response: Response) {

    } override fun onFailure(e: ApolloException) { } override fun onStatusEvent(event: ApolloCall.StatusEvent) { } })
  20. callbackFlow { enqueue( object : ApolloCall.Callback<T>() { override fun onResponse(response:

    Response<T>) { offer(response) } override fun onFailure(e: ApolloException) { close(e) } override fun onStatusEvent(event: ApolloCall.StatusEvent) { if (event = = ApolloCall.StatusEvent.COMPLETED) { close() } }
  21. callbackFlow { enqueue( object : ApolloCall.Callback<T>() { override fun onResponse(response:

    Response<T>) { offer(response) } override fun onFailure(e: ApolloException) { close(e) } override fun onStatusEvent(event: ApolloCall.StatusEvent) { if (event = = ApolloCall.StatusEvent.COMPLETED) { close() } }
  22. callbackFlow { enqueue( object : ApolloCall.Callback<T>() { override fun onResponse(response:

    Response<T>) { offer(response) } override fun onFailure(e: ApolloException) { close(e) } override fun onStatusEvent(event: ApolloCall.StatusEvent) { if (event = = ApolloCall.StatusEvent.COMPLETED) { close() } }
  23. callbackFlow { enqueue( object : ApolloCall.Callback<T>() { override fun onResponse(response:

    Response<T>) { offer(response) } override fun onFailure(e: ApolloException) { close(e) } override fun onStatusEvent(event: ApolloCall.StatusEvent) { if (event = = ApolloCall.StatusEvent.COMPLETED) { close() } }
  24. 1 2 3 A B C 1s 1s 2s 2s

    1A 2A 3C 3A 3B
  25. 1 2 3 A B C 1s 1s 2s 2s

    How did combine work?
  26. val receiveChannel = hello() 
 fun CoroutineScope.hello() = produce {

    while (true) { delay(300) send("Hello") } } Send Hello
  27. suspend fun selectHelloWorld(channelA, channelB) { select<Unit> { channelA.onReceive { value

    - > println(value) } channelB.onReceive { value - > println(value) } } }
  28. suspend fun selectHelloWorld(channelA, channelB) { select<Unit> { channelA.onReceive { value

    - > println(value) } channelB.onReceive { value - > println(value) } } } / / Hello
  29. suspend fun selectHelloWorld(channelA, channelB) { select<Unit> { channelA.onReceive { value

    - > println(value) } channelB.onReceive { value - > println(value) } } } / / World!
  30. suspend fun selectHelloWorld(channelA, channelB) { select<Unit> { channelA.onReceiveOrNull { value

    - > } channelB.onReceiveOrNull { value - > } } } Null when channelA closed
  31. suspend fun combine( . . . ) { val firstChannel

    = createChannel(firstFlow) val secondChannel = createChannel(secondFlow) }
  32. suspend fun combine( . . . ) { val firstChannel

    = createChannel(firstFlow) val secondChannel = createChannel(secondFlow) var firstValue: Any? = null var secondValue: Any? = null }
  33. suspend fun combine( . . . ) { val firstChannel

    = createChannel(firstFlow) val secondChannel = createChannel(secondFlow) var firstValue: Any? = null var secondValue: Any? = null var firstIsClosed = false var secondIsClosed = false }
  34. suspend fun combine( . . . ) { while (!firstIsClosed

    | | !secondIsClosed) { select<Unit> { } } }
  35. select<Unit> { channelA.onReceive { value - > firstValue = value

    if (secondValue ! = = null) { transform( . . . ) } } channelB.onReceive { value - > secondValue = value if (secondValue ! = = null) { transform( . . . ) } }
  36. select<Unit> { channelA.onReceive { value - > firstValue = value

    if (secondValue ! = = null) { transform( . . . ) } } channelB.onReceive { value - > secondValue = value if (firstValue ! = = null) { transform( . . . ) } }
  37. class MyViewModel(): ViewModel() { val states = MutableStateFlow(State()) fun updateState()

    { flowOfData .map { mapToState(data) } .onEach { state - > states.value = state } .launchIn(viewModelScope) } }
  38. class MyViewModel(): ViewModel() { val states = MutableStateFlow(State()) fun updateState()

    { flowOfData .map { mapToState(data) } .onEach { state - > states.value = state } .launchIn(viewModelScope) } }
  39. class MyViewModel(): ViewModel() { val states = MutableStateFlow(State()) fun updateState()

    { flowOfData .map { mapToState(data) } .onEach { state - > states.value = state } .launchIn(viewModelScope) } }
  40. class MyViewModel(): ViewModel() { val states = MutableStateFlow(State()) fun updateState()

    { flowOfData .map { mapToState(data) } .collect { state - > 
 states.value = state } / / logic after collect will run after it finishes } } Suspending
  41. class MyViewModel(): ViewModel() { val states = MutableStateFlow(State()) fun updateState()

    { flowOfData .map { mapToState(data) } .onEach { state - > states.value = state } .launchIn(viewModelScope) } }
  42. class MyViewModel(): ViewModel() { val states = MutableSharedFlow() fun updateState()

    { flowOfData .map { mapToState(data) } .onEach { state - > states.value = state } .launchIn(viewModelScope) } }
  43. class MyViewModel(): ViewModel() { val states = MutableSharedFlow() fun updateState()

    { flowOfData .map { mapToState(data) } .onEach { state - > states.emit(data) } .launchIn(viewModelScope) } }
  44. var counter = 0 suspend fun CoroutineScope.updateState() { val jobs

    = List(100) { launch { repeat(1000) { counter + + } } } }
  45. var counter = 0 suspend fun CoroutineScope.updateState() { val jobs

    = List(100) { launch { repeat(1000) { counter + + } } } }
  46. var mutext = Mutex() var counter = 0 suspend fun

    CoroutineScope.updateState() { val jobs = List(100) { launch { repeat(1000) { counter + + } } } }
  47. var mutext = Mutex() var counter = 0 suspend fun

    CoroutineScope.updateState() { 
 val jobs = List(100) { launch { repeat(1000) { mutext.lock() counter + + } } } }
  48. var mutext = Mutex() var counter = 0 suspend fun

    CoroutineScope.updateState() { 
 val jobs = List(100) { launch { repeat(1000) { mutext.lock() try { counter + + } finally { mutext.unlock() } }
  49. var mutext = Mutex() var counter = 0 suspend fun

    CoroutineScope.updateState() { List(100) { launch { repeat(1000) { mutext.lock() try { counter + + } finally { mutext.unlock() } }
  50. fun <T> Mutex.withLock(owner, action: () - > T): T {

    lock(owner) try { return action() } finally { unlock(owner) } }
  51. var mutext = Mutex() var counter = 0 suspend fun

    CoroutineScope.updateState() { List(100) { launch { repeat(1000) { mutext.withLock { counter + + } 
 } } }
  52. sealed class CounterEvents { object IncCounter : CounterEvents() class GetCounter(

    val response: CompletableDeferred<Int> ): CounterEvents() }
  53. fun CoroutineScope.counterActor() = actor<CounterEvents> { var counter = 0 for

    (event in channel) { when (event) { is IncCounter - > counter + + } } }
  54. fun CoroutineScope.counterActor() = actor<CounterEvents> { var counter = 0 for

    (event in channel) { when (event) { is IncCounter - > counter + + is GetCounter - > event.response.complete(counter) } } }
  55. val actor = counterActor() suspend fun CoroutineScope.updateState() { List(100) {

    launch { repeat(1000) { actor.send(IncCounter()) 
 } } }
  56. Resources • Articles on testing flows, coroutine, context and more!

    
 
 https: / / codingwithmohit.com/posts/ • Talk on corotutines! 
 
 https: / / codingwithmohit.com/talks/