How would I use kotlinx.coroutines in combination ...
# coroutines
m
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
If it has async APIs you can use coroutines adapters (existing or custom one), otherwise runBlocking
m
but doesn't runBlocking... block? that'll prevent background tasks from running won't it?
here's my code:
Copy 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
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
Is there any code I can reference for this? the javadoc is either pretty barebones or way too complex on most of coroutines
l
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
Do you have a link to the relevant page? I couldn't find anything there
g
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
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
So,
withContext
m
so I just use
withContext(Dispatchers.Main) { myBlockingCode() }
?
g
It will block main thread
l
I guess lwgl has an executor, which you can then convert for use in
withContext
using the
asCoroutineDispatcher()
extension function
m
So how do I run the UI without blocking the background tasks
l
The blocking code you're referring to, how long do you expect it to block?
m
the UI will block until the user closes the application of course
l
And what kind of thing is it doing?
👆🏼 1
m
so I guess it could block anywhere between a few minutes and a couple of hours
g
So it's main UI loop of the app
l
I'ts basically running the entire app UI, is that right?
m
correct, though there are async background tasks running that are essential to the application.
g
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
so how would I change my code to do that? In my code provided,
spawn
simply calls launch {} on the current coroutineScope
l
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
I believe you need something like: fun main() { GlobalScope.launch(Dispatchers.Default) { myBackgroundSuspendTask() } mainLoop() }
l
I have to add that I don't fully grasp what you're doing, so I might be bringing wrong advice
g
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
@gildor so that snippet will run all background jobs even though the main thread blocks, correct?
g
Yes
m
Is there anything I need to be wary of with thread-safety with this setup?
g
What do you mean specifically?
m
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
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
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
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
got it
g
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
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:
Copy code
glfwSetKeyCallback(window) {window, key, scancode, action, mods ->
			mainScope.launch {
				val result = doSuspendingMethod()
                updateUI(result) // This is running on main thread
		});
g
// 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
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
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)
322 Views