https://kotlinlang.org logo
Title
m

martmists

02/13/2022, 3:01 PM
How would I use kotlinx.coroutines in combination with a blocking framework like lwjgl? I can't find anything in the docs about cases like this
g

gildor

02/13/2022, 3:40 PM
If it has async APIs you can use coroutines adapters (existing or custom one), otherwise runBlocking
m

martmists

02/13/2022, 3:52 PM
but doesn't runBlocking... block? that'll prevent background tasks from running won't it?
here's my code:
@JvmStatic
fun main(args: Array<String>) = runBlocking {
    run()
}

override suspend fun run() {
    coroutineScope {
        ClientConnection(factory.connect("localhost", 22222)).apply {
            spawn()
        }

        launch {
            mainLoop()  // LWJGL initialization and stuff goes here, not sure how to force this on main thread though in case that's needed. This function also blocks and the connection established above halts.
        }
    }
}
l

louiscad

02/13/2022, 3:54 PM
If you need to block, you can use
runBlocking
, if you need to suspend over blocking code, you can use
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
There's facilites for both sides, basically.
And if you have callbacks, you can use
suspendCancellableCoroutine
, or
callbackFlow
for repeat callbacks.
m

martmists

02/13/2022, 3:57 PM
Is there any code I can reference for this? the javadoc is either pretty barebones or way too complex on most of coroutines
l

louiscad

02/13/2022, 3:59 PM
Instead of limiting yourself to KDocs, I recommend you to read the guide on kotl.in/coroutines to understand it all
There, you should end up learning about how to bridge blocking and suspending code together
m

martmists

02/13/2022, 4:02 PM
Do you have a link to the relevant page? I couldn't find anything there
g

gildor

02/13/2022, 4:03 PM
It's hard to recommend a particular approach without knowledge of how it is supposed to work without coroutines
Maybe you could provide a bit of details about what you are trying to achieve
m

martmists

02/13/2022, 4:05 PM
I'm trying to run blocking code from a coroutine context. Specifically, I'm using ktor networking and a fair share of other libraries that all support async, but I want to use lwjgl as UI framework. As such, I need to run blocking code from an async context, as the async background tasks are already started with
launch
in the same
coroutineScope
block.
I'm effectively looking for something similar to python's
loop.run_in_executor
that runs in the main thread, as that doesn't block the background tasks
l

louiscad

02/13/2022, 4:06 PM
So,
withContext
m

martmists

02/13/2022, 4:07 PM
so I just use
withContext(Dispatchers.Main) { myBlockingCode() }
?
g

gildor

02/13/2022, 4:07 PM
It will block main thread
l

louiscad

02/13/2022, 4:07 PM
I guess lwgl has an executor, which you can then convert for use in
withContext
using the
asCoroutineDispatcher()
extension function
m

martmists

02/13/2022, 4:08 PM
So how do I run the UI without blocking the background tasks
l

louiscad

02/13/2022, 4:08 PM
The blocking code you're referring to, how long do you expect it to block?
m

martmists

02/13/2022, 4:08 PM
the UI will block until the user closes the application of course
l

louiscad

02/13/2022, 4:08 PM
And what kind of thing is it doing?
👆🏼 1
m

martmists

02/13/2022, 4:09 PM
so I guess it could block anywhere between a few minutes and a couple of hours
g

gildor

02/13/2022, 4:10 PM
So it's main UI loop of the app
l

louiscad

02/13/2022, 4:10 PM
I'ts basically running the entire app UI, is that right?
m

martmists

02/13/2022, 4:10 PM
correct, though there are async background tasks running that are essential to the application.
g

gildor

02/13/2022, 4:10 PM
Run task in a separate coroutine, without blocking main thread with it
So use launch to run your background task, not to run your main loop, which anyway blocks main thread
m

martmists

02/13/2022, 4:11 PM
so how would I change my code to do that? In my code provided,
spawn
simply calls launch {} on the current coroutineScope
l

louiscad

02/13/2022, 4:13 PM
I'd try
withContext(Dispatchers.Main)
or with
asCoroutineDispatcher()
and see if it's doing what I want. It seems that you're doing it the other way around, running UI stuff coming from a background thread.
g

gildor

02/13/2022, 4:13 PM
I believe you need something like: fun main() { GlobalScope.launch(Dispatchers.Default) { myBackgroundSuspendTask() } mainLoop() }
l

louiscad

02/13/2022, 4:14 PM
I have to add that I don't fully grasp what you're doing, so I might be bringing wrong advice
g

gildor

02/13/2022, 4:15 PM
GlobalScope can be replaced with any app-level scope, depending on what kind lifecycle it should have, you also can cancel this job after mainLoop, to explicitly shutdown this background task when main loop is completed
m

martmists

02/13/2022, 4:15 PM
@gildor so that snippet will run all background jobs even though the main thread blocks, correct?
g

gildor

02/13/2022, 4:15 PM
Yes
m

martmists

02/13/2022, 4:15 PM
Is there anything I need to be wary of with thread-safety with this setup?
g

gildor

02/13/2022, 4:16 PM
What do you mean specifically?
m

martmists

02/13/2022, 4:17 PM
If the main UI thread needs to send data to the background jobs, is there anything I should know, similar to when sharing data using threading?
g

gildor

02/13/2022, 4:17 PM
It depends on how you want to send this data between threads
For example you can use Flow or Channel which are already thread safe
m

martmists

02/13/2022, 4:18 PM
alright, got it
also, when providing a dispatcher to
launch
,I get this warning from sonarlint:
SonarLint: Remove this dispatcher. It is pointless when used with only suspending functions.
Is this a bug in sonarlint or is the dispatcher parameter not necessary here?
g

gildor

02/13/2022, 4:20 PM
No, it makes sense in general, but in this case you block main thread, so you cannot use it anymore for anything else, coroutines dispatchet on the main thread will be blocked and never started
m

martmists

02/13/2022, 4:21 PM
got it
g

gildor

02/13/2022, 4:24 PM
In general it's possible to unlock the main thread, as soon as the main loop supports it, so it not just block, but also allows dispatching coroutines from the main dispatcher, but you need some kind integration with main loop
Usually it's done when the main loop provides some API which allows to pass tasks to it
It's how other main thread implementations work (you can check how Android or Swing main threads integrated with coroutines using those platform main thread executors)
With this setup you can easily use Main dispatcher and run any code from coroutines which requires main thread to be run, usually to interact with UI
n

Nick Allen

02/13/2022, 9:58 PM
Do your setup just like you would without coroutines (call the event loop directly from main). Just because you are using coroutines doesn't change how you call blocking code (
mainLoop
) from blocking code(
main
). You can see https://github.com/Kotlin/kotlinx.coroutines/tree/master/ui/kotlinx-coroutines-swing for how to make
Dispatchers.Main
run code within a particular event loop. Replace
SwingUtilities.invokeLater
and the like with equivalent LWJGL code. Make sure you register the service in META-INF. If you don't care about hooking up
Dispatchers.Main
, then you can just create a simple
Executor
that posts to the main event loop, and use
myMainPostingExecutor.asCoroutineDispatcher()
instead of
Dispatchers.Main
. Create a coroutine scope:
val mainScope = MainScope() + Dispatchers.Main.immediate
(or
val mainScope = CoroutineScope(SupervisorJob()) + myMainPostingExecutor.asCoroutineDispatcher()
) Then in your event callbacks you can do something like:
glfwSetKeyCallback(window) {window, key, scancode, action, mods ->
			mainScope.launch {
				val result = doSuspendingMethod()
                updateUI(result) // This is running on main thread
		});
g

gildor

02/14/2022, 6:39 AM
// This is running on main thread
It doesn’t run on main thread, it runs on myMainPostingExecutor Own dispatcher is definitely one of ways to work, but it’s not a real main thread, you cannot send events back to UI with it
then you can just create a simple 
Executor
 that posts to the main event loop
If you can send events to main loop from another thread, then I think it’s better and easier to implement Disaptchers.Main
n

Nick Allen

02/14/2022, 7:26 AM
Own dispatcher is definitely one of ways to work, but it’s not a real main thread, you cannot send events back to UI with it
This is false, or at least misleading/confusing. It’s a real main thread, reguardless of how you post events to it. Hooking up
Dispatchers.Main
is the standard and clearest way to do it. I missed explicitly recommending it.
g

gildor

02/14/2022, 4:00 PM
Agree, my message was not worded correctly, I just meant that it depends on implementation details of this dispatcher, and just creating a new executor and put it to a scope will not make it main dispatcher, and only explicitly integrate an executor with main loop will solve the issue, which I believe the biggest challenge for this case (except you already have such working executor)