What is the Purpose of the Main Dispatch ...

What is the Purpose of the Main Dispatcher?

Mar 21, 2024

If you have this question, perhaps you are a long time server-side developer and are wondering what the Main Dispatcher is doing in the Kotlinx coroutines library? Well, the Main Dispatcher exists as kind of an interface, but its implementation isn't really there.

The Main dispatcher is used in Kotlin when programming something that will interact with some sort of UI. For the Dispatchers.Main scope to be available, one of these needs to be available: Android main thread dispatcher, JavaFx, or Swing EDT dispatcher. The Main dispatcher has one distinct characteristic that tells it apart from the others. This dispatcher is singe-threaded and it is not shipped with the kotlinx coroutines library.

Let’s look at one example:

class MainDispatcherLauncher {

companion object {

val logger: Logger = LoggerFactory.getLogger(MainDispatcherLauncher::class.java)

@JvmStatic

fun main(args: Array<String> = emptyArray()) = runBlocking {

runMainCoroutinesTest()

}

private suspend fun runMainCoroutinesTest() {

try {

val job = CoroutineScope(Dispatchers.Main).launch {

launch {

delay(100)

logger.info("This is cat @ ${LocalDateTime.now()}")

}

launch {

logger.info("This is mouse @ ${LocalDateTime.now()}")

}

logger.info("This is master @ ${LocalDateTime.now()}")

}

job.join()

} catch (ex: IllegalStateException) {

logger.info("Error!", ex)

}

}

}

}

In this case, both launches would kickstart two coroutines and we would expect at least 3 log outputs in a normal situation. However, this will not work if simply running a standalone application or in a server-side setting. Since the Main disptacher isn’t available, running this code will result in:

Exception in thread "main" java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'

at kotlinx.coroutines.internal.MainDispatchersKt.throwMissingMainDispatcherException(MainDispatchers.kt:77)

at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:108)

at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.isDispatchNeeded(MainDispatchers.kt:92)

at kotlinx.coroutines.test.internal.TestMainDispatcher.isDispatchNeeded(TestMainDispatcher.kt:31)

at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:315)

at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)

at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:21)

at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:88)

at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:123)

at kotlinx.coroutines.BuildersKt_BuilderscommonKt.launch(Builders.common.kt:52)

at kotlinx.coroutines.BuildersKt.launch(Unknown Source)

at kotlinx.coroutines.BuildersKt_BuilderscommonKt.launch$default(Builders.common.kt:43)

at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)

at org.jesperancinha.ktd.MainScopeLauncher$Companion.runMainCoroutinesTest(MainScopeLauncher.kt:20)

at org.jesperancinha.ktd.MainScopeLauncher$Companion.access$runMainCoroutinesTest(MainScopeLauncher.kt:10)

at org.jesperancinha.ktd.MainScopeLauncher$Companion$main$1.invokeSuspend(MainScopeLauncher.kt:15)

at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)

at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)

at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95)

at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69)

at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)

at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)

at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)

at org.jesperancinha.ktd.MainScopeLauncher$Companion.main(MainScopeLauncher.kt:14)

at org.jesperancinha.ktd.MainScopeLauncher.main(MainScopeLauncher.kt)

Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@2c039ac6, Dispatchers.Main]

06:44:49.864 [main] INFO org.jesperancinha.ktd.MainScopeLauncher -- Error!

java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'

at kotlinx.coroutines.internal.MainDispatchersKt.throwMissingMainDispatcherException(MainDispatchers.kt:77)

at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:108)

at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.isDispatchNeeded(MainDispatchers.kt:92)

at kotlinx.coroutines.test.internal.TestMainDispatcher.isDispatchNeeded(TestMainDispatcher.kt:31)

at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:315)

at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)

at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:21)

at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:88)

at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:123)

at kotlinx.coroutines.BuildersKt_BuilderscommonKt.launch(Builders.common.kt:52)

at kotlinx.coroutines.BuildersKt.launch(Unknown Source)

at kotlinx.coroutines.BuildersKt_BuilderscommonKt.launch$default(Builders.common.kt:43)

at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)

at org.jesperancinha.ktd.MainScopeLauncher$Companion.runMainCoroutinesTest(MainScopeLauncher.kt:20)

at org.jesperancinha.ktd.MainScopeLauncher$Companion.access$runMainCoroutinesTest(MainScopeLauncher.kt:10)

at org.jesperancinha.ktd.MainScopeLauncher$Companion$main$1.invokeSuspend(MainScopeLauncher.kt:15)

at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)

at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)

at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95)

at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69)

at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)

at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)

at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)

at org.jesperancinha.ktd.MainScopeLauncher$Companion.main(MainScopeLauncher.kt:14)

at org.jesperancinha.ktd.MainScopeLauncher.main(MainScopeLauncher.kt)

Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: null

Process finished with exit code 0

The Main dispatcher can be found in the following modules: kotlinx-coroutines-android, kotlinx-coroutines-javafx and kotlinx-coroutines-swing .

You can find this example in my GitRepo:

https://github.com/jesperancinha/jeorg-kotlin-test-drives

Then go to :

jeorg-kotlin-coroutines/coroutines-crums-group-1/src/main/kotlin/org/jesperancinha/ktd/MainDispatcherLauncher.kt

Remember to check the source for more details:

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html

And if you have read this and have learned something new, you probably are ready to answer the following quiz:

https://youtube.com/shorts/VIITIP4-WWU?feature=share

And you can check your answer in the response video I created:

https://youtube.com/shorts/vr8lVaF4EQw?feature=share

Have a good one everyone!

Enjoy this post?

Buy João Esperancinha a coffee

More from João Esperancinha