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

frankelot

06/21/2021, 7:32 PM
Is there a way to
send
something to an actor and “suspend” until the message was processed?
According to the docs `channel.send`:
Copy code
suspending the caller while the buffer of this channel is full
I want to suspend more time, suspend until the actor picked up the message and processed it (picked up another one)
c

Casey Brooks

06/21/2021, 7:41 PM
Waiting for the actor to complete kinda defeats the purpose of the pattern. Rather, the actor should just post a message to another actor when it is finished, making a “chain” of actors, each with a specific purpose but not directly tied to any others. Alternatively, a cold
Flow
is designed around the idea of the sender suspending until the collector finishes processing. If you’re already using an actor pattern, it might be difficult to convert it to a Flow, however, but might be something to look into
f

frankelot

06/21/2021, 7:45 PM
Right, I might be using an actor to synchronise a resource right now
I’m starting to think it’s not the right tool for the job
using another actor would work.. but I’m sure there’s an easier/cleaner way
How would you use a Flow in this scenario?
c

Casey Brooks

06/21/2021, 7:53 PM
Yeah, don’t introduce an abstraction you don’t need. Normal suspending functions are designed for simplifying sequences of async operations, and things like actors and Flows are built on top of that mechanism with very specific goals in mind for their usage. It’s not like coroutines are low-level things and actors/flows/etc. are high-level APIs to them. Coroutines are already a high-level API Have you tried just using normal coroutines stuff (
coroutineScope.launch { }
)?
f

frankelot

06/21/2021, 7:58 PM
Agreed. I am, here’s roughly my issue
Copy code
val filter = BlurFilter() // expensive to create

fun render(value : Float) = launch {
   filter.value = value
   renderer.render(bitmap, filter) // Suspending, takes some time
}

// main:
for (i in 0..100) {
   render(Math.nextFloat())
}
launching multiple coroutines overrides the value on the shared resorce
filter
(render runs in its own thread/dispatcher)
c

Casey Brooks

06/21/2021, 8:03 PM
Yeah, coroutines’ normal functionality should be able to cover anything you’d need from this. For example:
Copy code
val filter = BlurFilter() // expensive to create
suspend fun render(value : Float) {
   filter.value = value
   renderer.render(bitmap, filter) // Suspending, takes some time
}
// main:
launch { 
    for (i in 0..100) {
       render(Math.nextFloat())
    }
}
Since render is marked
suspend
, its caller will wait until it completes before continuing. In this example, that means each iteration of the
for
loop in
main
will “suspend” until
render
returns, so the next iteration of the loop will only run once
render
has finished and the filter is available for use again.
f

frankelot

06/21/2021, 8:06 PM
Thank you, that would defenitely work! I’m not sure if I can apply this to my project since
render
gets called from very different places 🤔 but I’ll give it a try
c

Casey Brooks

06/21/2021, 8:12 PM
I’d suggest digging a bit deeper into coroutines to really understand how to use them well. You might also consider whether the filter should actually be a shared resource. It may be “expensive” to create, but is it noticeably expensive to the end-user? And compare its creation time with the memory it holds while keeping it also during your whole application. You might be dealing with a case of premature optimization, trying to fix something that isn’t actually an issue in practice. It might be perfectly OK to create a new instance of the filter every time you need to use it
f

frankelot

06/21/2021, 8:31 PM
I know the filter is expensive to create because it’s from a library of mine that under the hood initialises and sets the state to some OpenGL resources
Your idea of using
launch {
at the top is kinda what I intended to do when using an actor 🙂… I can call render from multiple places and just send a message to the actor to process it
The problem know, I know need to know when the processing ended.. I could use another actor as you suggested… but I’d like to stick to the suspending nature of coroutines, where things read sequentially (even if they don’t really are)
There’s this other approach that works… since I control the library I changed
Copy code
suspend fun render(target: Bitmap, filters: List<Filter>) {}
to
Copy code
suspend fun render(target: Bitmap, filters: () -> List<Filter>) {}
and since the render runs in its own unique GL thread, no need for extra synchronisation… (not even for actors)…
*sorry I might not be providing enough context for you to understand 🙂 in any case, thanks for the tips suggestions, sometimes explaining your problem to someone is all you need
c

Casey Brooks

06/21/2021, 9:54 PM
The whole idea of the actor pattern is kind of a “fire-and-forget” pipeline. Pass a message to an actor, which forwards to another actor, etc. until the whole pipeline has completed. The fact that you don’t know specifically when a given actor will be processing a message is its feature. It’s usually more useful in large distributed/decoupled systems, and the
actor
function in the kotlinx.coutines library has been deprecated for quite a while now which suggests there are better ways to express your intent with other coroutines features. The use-case you have and what you’re looking for definitely sounds more like standard suspending functions are a better way to go. There are several ways you could go with synchronizing the shared filter object that do not break the nice sequential nature of suspend functions: Mutex or Semaphore, for example, which you’d normally use in threading synchronization, but tailored for coroutines. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/index.html
e

Erik

06/22/2021, 5:39 AM
Is the solution proposed by @Casey Brooks thread safe? If multiple threads call
render
, then while one is suspended, another might set
filter.value = value
concurrently. I don't know if that value is read instantly on set, but otherwise could this lead to race conditions? If so, consider using a rendezvous channel, a mutex or another synchronization primitive, so that operations are atomically processed.
f

frankelot

06/22/2021, 12:27 PM
@Casey Brooks exactly, I think you are right a Mutex or Semaphore might work… my solution was just to force myself to mutating the filters in the renderer unique thread.. I’m ok with that
3 Views