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

addamsson

08/18/2019, 10:19 AM
I'm trying to write an API which will be called from Java so I'm using this construct:
Copy code
runBlocking {
    withContext(someContext) {
        // ...
    }
}
My problem is that these functions will be called from each other which would lead to nested `runBlocking`/`withContext` calls. I tried how this works here:
Copy code
runBlocking {
    println("In runBlocking 0")
    println("Current thread: ${Thread.currentThread().name}")
    withContext(Dispatchers.Single) {
        println("In withContext 0")
        println("Current thread: ${Thread.currentThread().name}")
        runBlocking {
            println("In runBlocking 1")
            println("Current thread: ${Thread.currentThread().name}")
            withContext(Dispatchers.Single) {
                println("In withContext 1")
                println("Current thread: ${Thread.currentThread().name}")
            }
        }
    }
}
but the problem is that here
In withContext 1
never gets printed. What is the best practice when one needs to nest these calls but has to keep the API accessible from Java?
l

louiscad

08/18/2019, 10:27 AM
Why do you use
withContext
there? And why do you nest
runBlocking
?
a

addamsson

08/18/2019, 10:30 AM
i need to use
withContext
because I use a single threaded dispatcher to make sure that all calls are executed in sequence
i'm modifying shared mutable state
runBlocking
is necessary because this will be called from Java
i could use actors instead but they are deprecated in Kotlin
d

Dominaezzz

08/18/2019, 11:50 AM
You can also do
runBlocking(someContext)
to elide the
withContext
.
In withContext 1
never gets printed because of deadlock.
The reason is similar to why there's no
runBlocking
in javascript but I can't articulate it.
a

addamsson

08/18/2019, 11:54 AM
why is there a deadlock?
d

Dominaezzz

08/18/2019, 11:54 AM
Basically the second
withContext
is waiting for the first
withContext
to finish.
a

addamsson

08/18/2019, 11:54 AM
oh wait
it tries to block until the second one finishes, but it can't since its parent is also blocking and the dispatcher executes serially
right?
d

Dominaezzz

08/18/2019, 11:55 AM
Yeah
a

addamsson

08/18/2019, 11:55 AM
i see the problem
i think i'll just implement
CoroutineScope
and use
launch
instead
d

Dominaezzz

08/18/2019, 11:56 AM
You can always return
CompletableFuture<...>
to java (eliminating the need for
runBlocking
).
a

addamsson

08/18/2019, 11:56 AM
i can call
launch
within another
launch
i'm in an MPP environment
so my
runBlocking
is just an
actual
function
I use
TODO()
in javascript 😄
d

Dominaezzz

08/18/2019, 11:57 AM
Ha
a

addamsson

08/18/2019, 11:57 AM
what returns
CompletableFuture
?
how would that look like?
i'm trying to avoid creating problems when I call a function which launches a coroutine from another function which launches its own
but in theory coroutines get nested so it is not a problem
d

Dominaezzz

08/18/2019, 11:58 AM
You can make an `expect`/`actual`
typealias
for
CompletableFutute<...>
,
Promise<....>
and
Deferred<....>
. Although I haven't tried this myself.
i'm trying to avoid creating problems when I call a function which launches a coroutine from another function which launches its own
Can you re-articulate this?
a

addamsson

08/18/2019, 11:59 AM
this would work, right?
Copy code
class Foo(
        override val coroutineContext: CoroutineContext = Dispatchers.Single)
    : CoroutineScope {

    fun fun0() {
        launch { 
            fun1()
        }
    }
    
    fun fun1() {
        launch { 
            // ...
        }
    }
}
the main point is that my users will call both
fun0
and
fun1
d

Dominaezzz

08/18/2019, 12:00 PM
Oh, is that what you mean.
a

addamsson

08/18/2019, 12:00 PM
but both need to work with coroutines
d

Dominaezzz

08/18/2019, 12:00 PM
I think, you might be better of with a wrapper class.
a

addamsson

08/18/2019, 12:01 PM
how would that work?
d

Dominaezzz

08/18/2019, 12:01 PM
Where the underlying
Foo
is suspend but the exposed
Foo
just calls
runBlocking
on the underlying
Foo
,.
a

addamsson

08/18/2019, 12:01 PM
that's the problem
oh wait
the problem with that is that I don't want to have duplicated interfaces
so what i'm writing has a synchronous api
but it works with shared mutable state
d

Dominaezzz

08/18/2019, 12:04 PM
Oh, you just want to use coroutines to help with sync.
a

addamsson

08/18/2019, 12:04 PM
yes
although i've written a test
and to my biggest surprise
using
Mutex
was faster than thread confinement
and using the deprecated
actor
function was faster than a
Mutex
d

Dominaezzz

08/18/2019, 12:05 PM
I think
actor
is the way to go.
a

addamsson

08/18/2019, 12:06 PM
my problem with actors though is that it is deprecated and it needs a lot of boilerplate
i've watched several videos about coroutines in Kotlin
but I still don't know what happens if I do this:
Copy code
withContext(coroutineContext) {
    withContext(coroutineContext) {

    }
}
d

Dominaezzz

08/18/2019, 12:07 PM
That should no-op.
a

addamsson

08/18/2019, 12:07 PM
will there be a context switch?
or will it recognize that i'm in the same context?
i tried to decompile the java bytecode
d

Dominaezzz

08/18/2019, 12:07 PM
withContext
does a check for similar contexts.
a

addamsson

08/18/2019, 12:08 PM
but i ended up with a 28MB file
oh i see
so i should just drop the whole thing and use the deprecated
actor
function anyway?
and write the boilerplate?
d

Dominaezzz

08/18/2019, 12:09 PM
a

addamsson

08/18/2019, 12:11 PM
checking
thanks 👍
hm
i might be better off using a
Mutex
it is only marginally slower and I don't have much writes
d

Dominaezzz

08/18/2019, 12:15 PM
Yeah, that works too. Just remember to unlock it before calling another method.
a

addamsson

08/18/2019, 12:16 PM
i'd have a mutex in each object
which restrict writes to that specific object
d

Dominaezzz

08/18/2019, 12:21 PM
Yes, but if a method holding a lock calls another method that wants to acquire the lock then 💥 .
a

addamsson

08/18/2019, 12:22 PM
yep, but I don't do that
i'm only locking in like 2-3 methods which implement the most basic operations and everything else is built on top of them
d

Dominaezzz

08/18/2019, 12:22 PM
Oh okay,
a

addamsson

08/18/2019, 12:23 PM
i made this possible by using persistent data structures internally so all reads are consistent
and data is easy to transform
you mentioned
CompletableFuture
before
what returns this?
async
?
d

Dominaezzz

08/18/2019, 12:26 PM
future
GlobalScope.future { ... }
a

addamsson

08/18/2019, 12:27 PM
oh
i don't see this method
is this available in an MPP environment?
i only see
async
d

Dominaezzz

08/18/2019, 12:28 PM
It's available in
kotlinx-coroutines-jdk8
I think.
a

addamsson

08/18/2019, 12:28 PM
although i don't really need this because all my mutating functions are returning
Unit
i'm working on an MPP project
d

Dominaezzz

08/18/2019, 12:28 PM
Yeeah
You don't need this
a

addamsson

08/18/2019, 12:29 PM
👍
the only thing i need is to block until the coroutine completes
that's what I don't know how to do without
runBlocking
i can just call
launch
in all my functions and be done with it but in that case the control is given back to the user without the operation finished
d

Dominaezzz

08/18/2019, 12:31 PM
There's no other way. Blocking is blocking. You still need to block?
a

addamsson

08/18/2019, 12:31 PM
the API is synchronous
d

Dominaezzz

08/18/2019, 12:31 PM
Or rather. Blocking is still an issue?
a

addamsson

08/18/2019, 12:31 PM
i just want some kind of way to make it thread-safe
another option is to have an `expect`/`actual` construct for
Deferred
which the user can call
await
on
the problem with
Deferred
is that
await
is
suspend
so it can't be used from Java
😒
d

Dominaezzz

08/18/2019, 12:35 PM
Then I guess you stuck with the wrapper class idea.
Or some variant of it.
a

addamsson

08/18/2019, 12:36 PM
that won't work
i'd need to duplicate a lot of the interfaces
why there is no built-in way in the coroutines api to help with java interop?
d

Dominaezzz

08/18/2019, 12:38 PM
Not the interfaces, just make an inner class.
Nope! Just
CompletableFuture
.
a

addamsson

08/18/2019, 12:38 PM
what do you mean by the inner class?
it might be easier to make an `expect`/`actual` class which provides a blocking
await
function
for Java users
d

Dominaezzz

08/18/2019, 12:40 PM
Copy code
class Foo : Interface1, Interface2 {
   fun stuff() = runBlocking { inten.stuff() }

   private class FooInternal {
        suspend fun stuff()
   }
}
Or maybe use a real
Mutex
.
a

addamsson

08/18/2019, 12:41 PM
like this?
Copy code
runBlocking {
    mutex.withLock {
        state = state.put(key, value)
    }
}
d

Dominaezzz

08/18/2019, 12:42 PM
Isn't that what you were already doing?
a

addamsson

08/18/2019, 12:42 PM
yeah, that's the problem 😄
plus this wont' work in my js implementation
man this is hard
d

Dominaezzz

08/18/2019, 12:42 PM
Or maybe use a real
Mutex
I meant a blocking one.
a

addamsson

08/18/2019, 12:43 PM
like
@Synchronized
?
d

Dominaezzz

08/18/2019, 12:43 PM
Yeah!
a

addamsson

08/18/2019, 12:43 PM
hm
and this would automatically work in js as well
since
@Synchronized
would be just ignored
d

Dominaezzz

08/18/2019, 12:44 PM
Just no-op in JS, since no concurrency anyway.
a

addamsson

08/18/2019, 12:44 PM
yep
why didn't I think about this earlier???
😂
d

Dominaezzz

08/18/2019, 12:45 PM
Aha, lol.
a

addamsson

08/18/2019, 12:45 PM
man you're a genius
thanks!
d

Dominaezzz

08/18/2019, 12:45 PM
No problem!
a

addamsson

08/18/2019, 12:46 PM
👍
Copy code
/**
 * Marks the JVM method generated from the annotated function as `synchronized`, meaning that the method
 * will be protected from concurrent execution by multiple threads by the monitor of the instance (or,
 * for static methods, the class) on which the method is defined.
 */
@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
@MustBeDocumented
@OptionalExpectation
public expect annotation class Synchronized()
so what this means is that if I have multiple
@Synchronized
functions in a class only one of them will have access to the monitor, eg: only one of them will be running at any given time
right?
d

Dominaezzz

08/18/2019, 1:27 PM
Yeah
a

addamsson

08/18/2019, 1:28 PM
this is exactly what i need
man this is awesome
i can delete so much code now
i just ran it through my benchmark and it is 8x times faster than a Kotlin Mutex
d

Dominaezzz

08/18/2019, 1:32 PM
Nice!
a

addamsson

08/18/2019, 1:52 PM
you can't imagine how happy i am now
l

louiscad

08/18/2019, 5:06 PM
Beware you can still have mutable shared state issues with a single thread as parallel coroutines are still running concurrently when they are not blocking the thread.
a

addamsson

08/18/2019, 9:55 PM
i skipped using coroutines altogether
how do you run coroutines in parallel on one thread?
that seems impossible
concurrent yes, parallel no
😮
b

bdawg.io

08/18/2019, 11:05 PM
Actor isn't deprecated. It's just being reworked for more complex scenarios which will make the existing APIs obsolete
I doubt they'll ever deprecate actors unless they find a reason to deprecate channels first
l

louiscad

08/19/2019, 6:08 AM
You're right, concurrent, not parallel.
a

addamsson

08/20/2019, 4:51 PM
I meant that the current API is obsolete so I can't use it without breaking my API after the come out with the new solution
b

bdawg.io

08/20/2019, 5:21 PM
They rarely release new APIs by removing all of the old ones if they can be made backwards compatible. Especially where the plan is to just expand the capabilities of actors, I see the current lambda APIs still being around for the simple cases
3 Views