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

Guilherme Lima Pereira

04/23/2021, 3:44 PM
Hi everyone! In Kotlin Coroutines, if I have a function that returns a
Deferred
, launched on
MainScope
, should I be able to
await
until it finishes? I mean, should it block the
Main Thread
(that’s my goal, even though it sounds weird)?
🙌🏽 1
👀 1
t

Tijl

04/23/2021, 3:46 PM
it will not block the main thread
await is a suspending function
g

Guilherme Lima Pereira

04/23/2021, 3:49 PM
thanks!
I’m looking for a way to achieve that. I’m creating a public function and I’d give the option for the consumer to consume it sync or async
t

Tijl

04/23/2021, 3:51 PM
wrap it in
runBlocking
g

Guilherme Lima Pereira

04/23/2021, 3:53 PM
That may work, I’ll try it! Thanks for the help 🙋
t

Tijl

04/23/2021, 3:55 PM
maybe good to point out that if you make a suspending API consumers can also just do this themselves. but if you want to provide a non-suspending API it’s an option. I just hope it won’t be blocking the main thread on my phone 😄
g

Guilherme Lima Pereira

04/23/2021, 3:57 PM
We’re trying to work with a timeout. The idea is to block by 800ms and then release the thread, but without stopping the execution. Kinda crazy, but we want to avoid crashing because of third party sdks 😕
e

ephemient

04/23/2021, 4:27 PM
n.b. do not use runBlocking on main thread https://github.com/Kotlin/kotlinx.coroutines/issues/2448
if it's on some other thread that doesn't belong to any coroutine dispatcher you should be ok
(but as a corollary, don't runBlocking from inside anything that suspends, either)
g

Guilherme Lima Pereira

04/23/2021, 6:02 PM
I’m heading to this kind of implementation:
Copy code
runBlocking {
    Log.d("GLP", "Started runBlocking")
    val result = withTimeoutOrNull(2_100) {
        Log.d("GLP", "Started withTimeoutOrNull")
        envVar.fetch().join()
        Log.d("GLP", "Finished withTimeoutOrNull")
    }

    if (result == null) {
        Log.d("GLP", "Reached timeout")
    }

    Log.d("GLP", "Finished runBlocking")
}
where
envVar.fetch()
returns a
Deferred<Unit>
(from a
async
call)
this way I won’t cancel the execution, but I won’t block the main thread until the execution finishes, but until it reaches the timeout (2100ms in this test scenario)
t

Tijl

04/23/2021, 8:51 PM
blocking the main thread is still very bad, even if it’s for 2100ms. that said using
runBlocking
on the main thread works just fine, only
runBlocking (Dispatchers.Main
on the main thread is problematic
e

ephemient

04/23/2021, 8:52 PM
runBlocking on the main thead... how sure are you that nothing you directly or indirectly call will want to switch context to Dispatchers.Main?
t

Tijl

04/23/2021, 8:57 PM
that goes for any Dispatcher and runBlocking
Incidentally using
Dispatchers.Main.immidiate
does work from inside
runBlocking
, e.g.
Copy code
runBlocking { // called from Main
   withContext(Dispatchers.Main.immidiate) {
   }
}
I presume the same goes for other
.immediate
versions of Dispatchers
e

ephemient

04/23/2021, 9:01 PM
yes, it does. the biggest difference is that you're not scheduling any other code on the IO pool's threads, so a simple "don't use runBlocking if you might be called from a suspend context" rule is enough, but everybody has access to Main
anyhow, using runBlocking or not, 2100ms is extremely long for the main thread to be blocked. that's 126000 dropped frames on most devices, and within a small factor of being killed by ANR too
t

Tijl

04/23/2021, 9:12 PM
Sometimes you can’t go around using
runBlocking
, even from a suspended context. E.g. inside
init{}
or
lazy{}
or
get/set
. Of course you should know what the code you call is doing but I think that rule generally should apply. For this reason, I hope they make
immediate
usable from
runBlocking
, it’s only a lack of implementation this does not work right now.
e

ephemient

04/23/2021, 9:35 PM
you can get around them, by not doing the work in the constructor or not using properties. but it would be nice to have lazy properties and delegates.
d

dewildte

04/23/2021, 11:47 PM
What are you actually trying to achieve.
t

Tijl

04/24/2021, 9:24 AM
Not doing initialisation in the constructor (if there’s no functional reason not to) is an anti-pattern, it’ll introduce an additional state for your class which is a source of bugs. That said, it can usually be solved by suspending “Constructor functions”, it’s true that is often better than using
runBlocking
. Still saying “be careful with using runBlocking, because it will block your thread” is better than “don’t use runBlocking if you might be called from a suspend context”. It simply goes waaay too far in my opinion. Especially the maybe there, but even then. Essentially coroutines have a dual purpose, they provide suspending functions, but they also are context scheduler. I end up using runBlocking here and there to schedule work in the correct context, rather than doing a suspended wait for some long running task.
e

ephemient

04/25/2021, 2:18 AM
I thought about it, but in the end I disagree. I've had to track down far too many issues in a large codebase where runBlocking did work at some point, but unrelated changes exposed deadlocks. It is a far better use of everyone's time to forbid runBlocking from anything in the call chain from a suspending function.
💯 2
g

Guilherme Lima Pereira

04/26/2021, 1:27 PM
Hey everyone, sorry for the confusion about how much time the blocking would last. In fact, it’ll be only
850ms
and not
2100ms
. @dewildte we created an internal SDK that returns a map. The goal of this map is to allow not initializing some SDK we know it’s crashing (e.g. what happened with Facebook). This map comes from a server and it should be fast (everything was optimized on it on server side, latency, cache, etc), but we won’t trust it, that’s why we’re putting an
850ms
withTimeout
block. Why
runBlocking
? I want to block the app initialization (Application class) until the fetch timeout. In the happy path, we would be able to not call the initialize function of some libraries we’ve wrapped. Does it make sense for you? Please, share your sincere opinion 😄
We don’t have any
runBlocking
call on our project, we’ve talked about it and it didn’t make sense until now. I’d be very happy if you have any idea on how implement it without
runBlocking
d

dewildte

04/26/2021, 1:49 PM
Perhaps make a splash screen and do not navigate to the next screen until you have the data required to do your work. The main point here is to never block the main thread because it locks up the entire UI. The goal is to communicate work in progress in other ways but never block the main thread.
g

Guilherme Lima Pereira

04/26/2021, 2:03 PM
Many libraries needs to be initialized on the
Application
, so dealing with it on a Splash screen won’t work for me, I think…
t

Tijl

04/26/2021, 2:09 PM
just put your minSdkVersion to Android 12 android dancehttps://developer.android.com/about/versions/12/features/splash-screen
😂 1
e

ephemient

04/26/2021, 2:23 PM
850ms is still ridiculously long to hang the main thread for. if it's in Application create, there's no UI yet - so you're not missing any frames, but it's also terrible, it'll prevent the UI from appearing (such as splash screen)
facebook sdk shouldn't require being loaded in application. what other sdks are you concerned about?
g

Guilherme Lima Pereira

04/26/2021, 8:06 PM
@ephemient for now we’re going with
runBlocking
(dealine x time to refactor) but your question was very important. I thought about it and I’ll organize a project to move some initializations to the first activity and then I can run things in parallel in there, without blocking the thread
t

Tijl

04/26/2021, 8:20 PM
be aware that on Android any activity can be your “first activity”
g

Guilherme Lima Pereira

04/26/2021, 9:07 PM
Our project only has one activity 🙌
9 Views