Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dispatch coroutines directly to main thread on the JVM?

I'm setting up a kotlin coroutine based networking framework for the jvm. The Client and Server classes implement CoroutineScope, and the override for coroutinecontext is Dispatchers.IO, as I am pretty sure that's the correct Dispatcher to use for such a case. However, I wish to handle read packets on the main thread, or at least provide that option. Without reading the documentation, I used Dispatchers.Main, which I now realize is for the android UI thread. Is there a dispatcher I can use to get a coroutine running on the main thread? If not, how would I go about making one?

I have looked around the kotlin documentation on how to create a dispatcher based around a single thread, but I couldn't find anything besides newSingleThreadContext which creates a new thread. I also figured out that it is possible to create a dispatcher from a java Executor, but I'm still not sure how to limit this to a already existing thread.

class AbstractNetworkComponent : CoroutineScope {
    private val packetProcessor = PacketProcessor()
    private val job = Job()
    override val coroutineContext = job + Dispatchers.IO
}

class PacketProcessor : CoroutineScope {

    private val job = Job()
    override val coroutineContext = job + Dispatchers.Main //Android only!
    private val packetHandlers = mutableMapOf<Opcode, PacketHandlerFunc>()

    fun handlePacket(opcode: Opcode, packet: ReceivablePacket, networker: Writable) {
        launch(coroutineContext) {
            packetHandlers[opcode]?.invoke(packet, networker)
        }
    }
}

So with the Dispatchers.Main I get an IllegalStateException due to the android component missing. Is there a way to create a dispatcher that blocks the main thread until its completion (like runBlocking does?) Thanks!

like image 944
Yonatan Ginsburg Avatar asked Jan 15 '19 01:01

Yonatan Ginsburg


People also ask

What is the difference between dispatchers main and default in coroutines?

As Dispatchers.Main is usually a different thread in UI apps, switching between Dispatchers.Default and Dispatchers.Main in coroutines doesn’t come with huge performance costs as the coroutine just suspends (i.e. stops executing in one thread), and gets scheduled to be executed in a different thread.

What is coroutinescheduler in Java?

The CoroutineScheduler, which is the thread pool used in the Java programming language implementation by default, distributes dispatched coroutines to worker threads in the most efficient manner. As Dispatchers.Default and Dispatchers.IO use the same thread pool, switching between them is optimized to avoid thread switches whenever possible.

Which thread starts the coroutine in the default thread?

It starts the coroutine in the IO thread, it is used to perform all the data operations such as networking, reading, or writing from the database, reading, or writing to the files eg: Fetching data from the database is an IO operation, which is done on the IO thread. It starts the coroutine in the Default Thread.

How to implement dispatcher in Java?

The implementation of dispatcher is straightforward. HandlerContext contains reference to the handle, which accepts main looper. And the override dispatch function takes runnable block and passes it to the handler. The runnable block contains the code, which was put into the continuation block that was mentioned on the image above.


2 Answers

runBlocking is exactly what you need. It creates a dispatcher and sets it in the coroutine context. You can access the dispatcher with

coroutineContext[ContinuationInterceptor] as CoroutineDispatcher

and then you can pass it to an object that implements CoroutineScope or whatever else you want to do with it. Here's some sample code:

import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlin.coroutines.ContinuationInterceptor

fun main() {
    println("Top-level: current thread is ${Thread.currentThread().name}")
    runBlocking {
        val dispatcher = coroutineContext[ContinuationInterceptor]
                as CoroutineDispatcher
        ScopedObject(dispatcher).launchMe().join()
    }
}

class ScopedObject(dispatcher: CoroutineDispatcher) : CoroutineScope {
    override val coroutineContext = Job() + dispatcher

    fun launchMe() = launch {
        val result = withContext(IO) {
            "amazing"
        }
        println("Launched coroutine: " +
                "current thread is ${Thread.currentThread().name}, " +
                "result is $result")
    }
}

This will print

Top-level: current thread is main
Launched coroutine: current thread is main, result is amazing
like image 66
Marko Topolnik Avatar answered Oct 18 '22 09:10

Marko Topolnik


As per Guide to UI programming with coroutines kotlinx.coroutines has three modules that provide coroutine context for different UI application libraries:

  • kotlinx-coroutines-android -- Dispatchers.Main context for Android applications.
  • kotlinx-coroutines-javafx -- Dispatchers.JavaFx context for JavaFX UI applications.
  • kotlinx-coroutines-swing -- Dispatchers.Swing context for Swing UI applications.

Also, UI dispatcher is available via Dispatchers.Main from kotlinx-coroutines-core and corresponding implementation (Android, JavaFx or Swing) is discovered by ServiceLoader API. For example, if you are writing JavaFx application, you can use either Dispatchers.Main or Dispachers.JavaFx extension, it will be the same object.

like image 31
Sergey Avatar answered Oct 18 '22 10:10

Sergey