IntelliJ Platform Plugin SDK Help

Coroutine Dispatchers

Coroutines are always executed in a context represented by CoroutineContext. One of the most important parts of the context is a dispatcher, which determines what thread or thread pool the corresponding coroutine is executed on.

In the IntelliJ Platform, coroutines are executed on three main dispatchers:

Default Dispatcher

The Dispatchers.Default dispatcher is used for performing CPU-bound tasks.

It ensures that the number of tasks running in parallel does not exceed the number of CPU cores. A hundred threads performing CPU-bound work on a machine with 10 CPU cores can result in threads competing for CPU time and excessive thread switching. This makes the IDE effectively slower, hence the limitation. Using the default dispatcher (or its limitedParallelism() slice) enables a consistent CPU load.

IO Dispatcher

The Dispatchers.IO dispatcher is used for performing IO operations like reading/writing to files, network, executing external processes, etc.

It must be used at the very deep moment in the trace right before the actual IO operation happens and exited as soon as the operation is finished. Example:

suspend fun readDataFromFile(): Data { return withContext(Dispatchers.IO) { val fileName = computeFileName() val bytes = readFile(fileName) Data(parseBytes(bytes)) } }
suspend fun readDataFromFile(): Data { val fileName = computeFileName() val bytes = withContext(Dispatchers.IO) { readFile(fileName) } return Data(parseBytes(bytes)) }

EDT Dispatcher

The Dispatchers.EDT dispatcher is used for executing UI actions on the Swing Event Dispatch Thread. Dispatchers.EDT dispatches onto EDT within the context modality state.

Dispatchers.Main vs. Dispatchers.EDT

In Kotlin, a standard dispatcher for UI-based activities is Dispatchers.Main. In the IntelliJ Platform, the EDT dispatcher is also installed as Dispatchers.Main so both can be used, however always prefer Dispatchers.EDT. Use Dispatchers.Main only if the coroutine is IntelliJ Platform-context agnostic (e.g., when it can be executed outside the IntelliJ Platform context). Use Dispatchers.EDT when in doubt.

Dispatchers vs. Threads

The dispatcher concept is a higher level of abstraction over threads. While the code is always executed on threads, do not think about dispatchers as specific thread instances.

A single coroutine is not bound to the same thread during the whole execution time. It may happen that a coroutine starts on thread A, is suspended, and finished on thread B, even if the whole is executed with the same dispatcher context.

Consider the following code snippet:

suspend fun doSomething() { val fetchedData = suspendingTask() withContext(Dispatchers.EDT) { updateUI(fetchedData) } } suspend fun suspendingTask(): Data { // fetch data from the internet }

The following diagram presents one of the potential execution scenarios:

suspendingTask() suspendingTask() updateUI() Thread 1Thread 2EDT

The code is executed as follows:

  1. suspendingTask is started and partially executed on Thread 2.

  2. suspendingTask is suspended when it waits for data fetched from the internet.

  3. After receiving data, suspendingTask is resumed, but now it is executed on Thread 1.

  4. Execution explicitly switches to the EDT dispatcher and updateUI is executed on EDT.

Last modified: 20 September 2024