https://kotlinlang.org logo
#coroutines
Title
# coroutines
a

Alexey Demedeckiy

06/13/2020, 7:08 PM
Hi! I am trying to implement some sort of event store / redux store using corutines in android application Here is the pseudocode:
Copy code
class Store {
  private val channel = Channel<Action>()

  suspend func dispatch(action: Action) {
    channel.send(action)
  }

  init {
    GlobalScope.launch {
      for (action in channel) {
        reduce(action)
        notifyObservers()
      }
    }
  }
}
Sadly, this code doesn't work - actions are not reducer. Small detail: If i will explicitly set Dispatchers.Main in init and in dispatch - it work, but defeats my purpose. My goal is to move action processing of main thread. 
notifyObservers
 will build 
Props
 for each active 
ViewModel
 and publish.
z

Zach Klippenstein (he/him) [MOD]

06/13/2020, 7:12 PM
actions are not reduced
Can you elaborate? Are they not being sent on the channel? Not being received from the channel? Is
reduce
not working as expected?
Also, don't use
GlobalScope
.
a

Alexey Demedeckiy

06/13/2020, 7:17 PM
dispatch
remains suspended,
reduce
never called as nothing was send to the channel. Why? And what should I use instead? I just want to offload processing from UI thread to not block UI while I calculate next props.
v

vaskir

06/13/2020, 7:21 PM
I think you need a buffered channel.
or, if you want to process actions one by one so that Main thread waits for the result, just
async(Dispatchers.Default) { reduce(action) }.await()
a

Alexey Demedeckiy

06/13/2020, 7:24 PM
Why? My understanding is that Randevouz channel will keep send suspended until action is not processed, and processor will be suspended while there is no actions in channel.
👍 1
Actions will arrive at any time, I don't want to wait for result of processing. I want to start additional coroutine on background that will process them for me
z

Zach Klippenstein (he/him) [MOD]

06/13/2020, 7:26 PM
Re GlobalScope, the kdoc on GlobalScope itself discourages its use, and there is a conversation in this channel every couple of days about why. This stackoverflow answer sums it up nicely: https://stackoverflow.com/a/54351785/1502069
You shouldn't need a buffered channel, that code looks like it should process the channel like you expect.
v

vaskir

06/13/2020, 7:27 PM
I don't see any need for a channel here then.
just execute actions on the Default scheduler, it's simpler.
z

Zach Klippenstein (he/him) [MOD]

06/13/2020, 7:29 PM
The difference between these two solutions is the channel approach processes actions from multiple senders sequentially, and the async approach processes them concurrently. Is that sequential behavior a requirement?
v

vaskir

06/13/2020, 7:29 PM
your solution allows executing actions one-by-one, like an actor. The ^^^ allows using CPU count parallel executions.
exactly
or, if actions are IO-bound, you can use Dispatchers.IO to do a lot of them in parallel.
a

Alexey Demedeckiy

06/13/2020, 7:30 PM
Yes, sequential processing is a strong must.
I need "Fan-In" processing, If I understand docs correctly.
v

vaskir

06/13/2020, 7:31 PM
ok, then show more code (who calls
dispatch
, who calls the Store ctor?)
a

Alexey Demedeckiy

06/13/2020, 7:32 PM
dispatch
is called by UI - whenever something happens - new action is sended to dispatch.
Store
ctor is created on application start - it is single instacnce per application
Also dispatch can be called from some IO operations, with result of the operation.
reduce
change global mutable state, so I need to protect it from parallel mutation - that's why sequential order is a must
z

Zach Klippenstein (he/him) [MOD]

06/13/2020, 7:34 PM
Does the
channel.send
function get invoked?
a

Alexey Demedeckiy

06/13/2020, 7:36 PM
Copy code
func dispatch(action: Action) {
  log("Sending")
  channel.send(action)
  log("Sended")
}
I see single "Sending" message but not "Sended"
z

Zach Klippenstein (he/him) [MOD]

06/13/2020, 7:40 PM
Can you post some actual code? You're typing
func
so I know you're not copy/pasting 😜
a

Alexey Demedeckiy

06/13/2020, 7:41 PM
Re GlobalScope> Thanks for links. I am still unsure what I should replace it with. Should I make
suspend init
? Should I use MainScope?
z

Zach Klippenstein (he/him) [MOD]

06/13/2020, 7:44 PM
I would create a
Copy code
private val scope = CoroutineScope(Dispatchers.Default)
In your class and use that. That makes it explicit, and also means if you need to ever dispose your
Store
class (eg to reset state between tests) you can just cancel that scope.
💯 1
a

Alexey Demedeckiy

06/13/2020, 7:44 PM
Yeah it is waaaay to simplified code. I rather want to check that my knowledge of corutines is correct. I did not "own" this codebase, just helps with architecture. I can try to craft minimal actual example and share to you, if you will have time to read the code. I hoped that I did some rookie mistake that somebody could see on the spot. But looks like I need to go on the hard road 🙂
z

Zach Klippenstein (he/him) [MOD]

06/13/2020, 7:45 PM
I'm guessing there's some detail in the actual code that is the culprit. The general idea looks fine.
a

Alexey Demedeckiy

06/13/2020, 7:47 PM
That is great news! I will experiment with clean from scratch example.
r

Robert Jaros

06/13/2020, 7:52 PM
instead of implementing your own, you can also try https://github.com/freeletics/CoRedux
a

Alexey Demedeckiy

06/13/2020, 8:00 PM
Cool! Same Idea! Will look for reference code, thanks.
3 Views