Console.WriteLine("Thread name " + Thread.CurrentThread.Name); // Main
var p = await DbHandler.Database.Accounts.Find(x => x.Id == id).FirstOrDefaultAsync();
player.Name = p.Name; // player.Name call native sync code and works in main
Console.WriteLine("Thread name2 "+Thread.CurrentThread.Name); // Main
I tried to do something equivalent in kotlin:
IdenCoroutine.launch(Dispatchers.Unconfined) {
println("Thread Name: ${Thread.currentThread().name}") // this is main
val user = getUser() // non-blocking await
p.name = user.name // p.name calls a native jni function that fails when executed in a thread other than main.
println("Thread name 2: " + "current thread is ${Thread.currentThread().name}") // this is DefaultDispatcher-worker-1 and fails p.name
}
suspend fun getUser() = withContext(Dispatchers.IO) {
// get user from db
}
But it fails because the coroutine sends p.name to another thread.
So the question is how can I replicate what I was doing in C# with Kotlin Coroutines?
Do you mean to jump to the main thread after returning from the getUser()? If so then why do you use Dispatchers.Unconfined instead of Dispatchers.Main?
Hmm, ok, so what is this “main” thread? Is it already used by some coroutine dispatcher? if not, do you have control on how it is created or maybe it is provided to you by some kind of library? If you don’t control it, is it some kind of an event-loop or is it just “some” thread?
I ask these questions, because coroutines need to have a way to schedule a task to be performed on a specific thread. Normally, you can’t just tell some thread to do something. Such thread has to cooperate, it has to provide a way to schedule tasks to it, so It has to be some kind of an event-loop - either operated by a coroutine dispatcher already or by some other library.
I don’t know how this C# code works exactly, but I believe it is technically impossible to execute something using just “any” thread, then release this thread to do something else and then jump back to it again. It is only possible if either the thread is managed by a concurrency framework of some sort (coroutine dispatcher, executor, our own event-loop implementation, etc.) or if we don’t really release the thread, but occupy it all the time by blocking it.
My guess is that this main thread of C# is a special thread that is managed by async/await machinery to make possible to jump to it.
Anyway, I don’t know what is your specific case, but you can start a thread, make it managed by coroutines and then use this thread as your “main” thread:
fun main() {
IdenCoroutine.launch(myMainDispatcher) {
println("Thread Name: ${Thread.currentThread().name}") // my-main-thread
val user = getUser() // non-blocking await
println("Thread name 2: " + "current thread is ${Thread.currentThread().name}") // my-main-thread
}
}
val myMainDispatcher = Executors.newSingleThreadExecutor {
thread(name = "my-main-thread", start = false, block = it::run)
}.asCoroutineDispatcher()
If you have to use already existing thread then you can make it “managed” by invoking runBlocking(), then block it forever and write your whole code inside this runBlocking() call. But this is probably not the usual use case for runBlocking() and there may be some drawbacks.
I realized that the API I use has an event loop called “onProcessTick”, now my only question would be how I could adjust this to handle it as a Dispatcher in Coroutines.
// IdenCoroutine == GlobalScope
IdenCoroutine.launch(Dispatchers.IO) {
// this is DefaultDispatcher-worker-1
ServerAPI.executeOnMainThread {
// Works, this is main thread
}
}
API code:
private val mainThreadTasks = ConcurrentLinkedQueue<() -> Unit>()
override fun onProcessTick() { // callback JNI
do {
tryAndCatch {
val task = mainThreadTasks.poll() ?: return
task.invoke()
}
} while (true)
}
override fun executeOnMainThread(action: () -> Unit) {
if (serverService.isOnMainThread()) {
action()
} else {
mainThreadTasks.add(action)
}
}