Home > Back-end >  How important is it to specify dispatchers/context in Kotlin coroutines? What happens if you don
How important is it to specify dispatchers/context in Kotlin coroutines? What happens if you don

Time:11-06

If a coroutine was launched and no dispatcher was specified (eg. GlobalScope.launch {}), what dispatcher is used?

If that coroutine was launched in the main thread, would it use Dispatchers.main?

Also, what happens if you do not specify dispatchers or context in your coroutines? Say you did database operations, but didn't specify Dispatchers.IO anywhere. Would that cause any major issues?

CodePudding user response:

If a coroutine was launched and no dispatcher was specified (eg. GlobalScope.launch {}), what dispatcher is used?

Kotlin coroutines follow structured concurrency. In general if no dispatcher is specified in the coroutine builder, you get the dispatcher from whichever parent scope you're starting your coroutine in. If there is no dispatcher in the parent scope, the coroutine will use Dispatchers.Default (see the doc of launch for instance).

How to find the parent scope:

  • if you see launch { .. } in the wild, some instance of CoroutineScope must be available as this in that piece of code, so that's the parent scope.
  • if you see scope.launch { ... }, look at that scope
  • if you see GlobalScope.launch { ... } then GlobalScope is the parent scope

If the scope is created with the CoroutineScope() factory function without particular dispatcher, Dispatchers.Default will be used as per the documentation.

If the scope is created using MainScope() and without particular dispatcher, it will use Dispatchers.Main.

If the scope is GlobalScope, then it doesn't have any dispatcher, so as mentioned before the coroutines will use Dispatchers.Default.

If the scope is lifecycleScope or viewModelScope provided by Android, then coroutines will use Dispatchers.Main.immediate by default.

If the scope is provided by runBlocking, then it will use a special dispatcher that works like an event loop and executes your coroutines in the thread that called runBlocking (which is nice because that thread would be blocked anyway).

If the scope is provided by another coroutine builder like launch or async (which means your coroutine is a child of that coroutine), then the dispatcher will be taken from the scope that was used to launch the parent coroutine, unless they override it. So you can go all the way up until you reach one of the options mentioned above:

parentScope.launch {
    // here, this = child scope that gets the dispatcher from parentScope
    
    launch {
        // inherits from parent launch
    }
    launch(Dispatchers.IO) {
        // here, this = child scope with overridden dispatcher
        launch {
            // inherits Dispatchers.IO from parent launch's scope
        }
    }
}

If that coroutine was launched in the main thread, would it use Dispatchers.main?

You probably can piece this together with the previous paragraph, but just to reiterate: it depends on the scope you're launching your coroutine in.

If you launch your coroutines from lifecycleScope/viewModelScope, then yes it will run on the main thread (in Dispatchers.Main.immediate).

If you call runBlocking from the main thread, then it would run coroutines on the main thread as well (but not in Dispatchers.Main), but you should definitely never do that in Android.

If you use something like MainScope() it would indeed run in Dispatcher.Main.

But in other cases, there is a high chance it runs on Dispatchers.Default - check the scope!

Also, what happens if you do not specify dispatchers or context in your coroutines? Say you did database operations, but didn't specify Dispatchers.IO anywhere. Would that cause any major issues?

As you can see from the beginning of this answer, the dispatcher used depends on how you coroutine is launched. If we assume you don't specify any dispatcher anywhere, there is a good chance your coroutines are running on Dispatchers.Main (in Android) or Dispatchers.Default.

Dispatchers.Main would be really bad for IO stuff, because it would freeze your UI while the IO is happening. I believe Android probably crashes in case you run the wrong things in this dispatcher, but I haven't done Android dev in a while, so I can't say for sure.

Dispatchers.Default is a shared thread pool that's sized based on the number of cores of the machine executing the code, so it's suitable for CPU-bound tasks. If you launch a bunch of coroutines that perform blocking IO in this dispatcher, you could block all your threads and prevent other coroutines from running, which is really not ideal - it could cause lags or slowness, especially if you rely a lot on coroutines.

Dispatchers.IO is not magic, but it will spawn more threads as necessary if too many threads are blocked, so that other coroutines can run. You will still incur the additional memory of extra threads, but other coroutines will be free to run while some threads are blocked on IO.

You can read more about how to use dispatchers in the documentation.

  • Related