https://kotlinlang.org logo
Title
r

r4zzz4k

01/16/2018, 7:31 PM
Of course I could add a level of indirection by using
CompletableDeferred
, thank you very much for this suggestion! Here's the final sample: https://try.kotlinlang.org/#/UserProjects/dsu5vhgcr0ls2h0flirmp3pf9g/ga94a9qaeq9e6v6s5bncbb75k3
b

bj0

01/16/2018, 8:25 PM
they are useful tools depending on the situation. from your example, it looks like you are mixing 2 separate solutions together. You can simplify it down to just:
val dispatcher = newSingleThreadContext("dispatch")

            val r1 = async(dispatcher) {
                log("Command 1 started")
                delay(1200)
                log("Command 1 finishing")
                "Command 1 result"
            }.await()
            log(r1)
            
            val r2 = async(dispatcher) {
                log("Command 2 started")
                delay(300)
                log("Command 2 finishing")
                "Command 2 result"
            }.await()
            log(r2)
r

r4zzz4k

01/16/2018, 8:58 PM
In the real code there are multiple queues, I want them to be separated and run in parallel. So I actually have
fixedThreadPool
and multiple `actor`s.
b

bj0

01/16/2018, 9:08 PM
yea
actor
is a good way to make sure stuff is executed serially if you have any suspension, but usually you do the actual execution of the "command" inside the actor, not just launch it in
async
and wait for it in the actor, that doesn't guarantee serialization
r

r4zzz4k

01/16/2018, 9:28 PM
Doesn't the
.await()
in the `actor`'s body guarantees that the task is done before the next one is pulled in?
async
coroutines that are sent to it are signalling response at their end, after the command body is executed, so from what I can see this should work.
actor
awaits for the coroutine itself,
post()
awaits for the
CompletableDeferred
which is completed at the very end of the coroutine.
b

bj0

01/16/2018, 9:45 PM
it might be true for
LAZY
, I've never used that. For normal
async {}
it will not be true if your command has any type of suspend, then other queue'd `async {}`'s could start during that suspension
r

r4zzz4k

01/16/2018, 10:01 PM
Oh, I believe I understood your point. According to the docs coroutines launched via
async
marked as
LAZY
are actually run after the
.await()
call (or
.join()
/
.start()
) -- till that point they will just hang out there waiting to be started, exactly what I need.
b

bj0

01/16/2018, 10:03 PM
ah, thats interesting, though it might be a little confusing just reading the code
you can get the same effect with something like:
fun commandQueue(context: CoroutineContext) = actor<suspend () -> Unit>(context = context) {
        log("Command queue started")
        for (operation in channel)
            operation()
        log("Command queue finished")
    }

    suspend fun <T> SendChannel<suspend () -> Unit>.post(command: () -> T): Deferred<T> {
        val completable = CompletableDeferred<T>()
        this.send { completable.complete(command()) }
        return completable
    }
and you can
typealias
the function signature to make it look cleaner
r

r4zzz4k

01/16/2018, 10:24 PM
In this case
actor
runs within separate context and so are commands? Seems much cleaner, I agree. Will check that tomorrow as I'm already on my way home, thanks! It's hard to switch to thinking in terms of neat concurrency of coroutines and channels after more than a year of C++ and dirty hand-managed parallelism :) Thank you again for your help, much appreciated!
b

bj0

01/16/2018, 10:49 PM
yea it definitely takes some thought, but once you start to get it, it really is quite elegant. good luck
and yes, by specifying the
newSingleThreadContext
as
CoroutineContext
to the
actor
, the
actor
will only run on that thread, which saves you from having to pass the dispatcher around to other places