Контекст корутины включает себя такой элемент как диспетчер корутины. Диспетчер корутины определяет какой поток или какие потоки будут использоваться для выполнения корутины.
Все построители корутины, в частности, функции launch
и async
в качестве необязательного параметра принимают
объект типа CoroutineContext, который может использоваться для определения диспетчера создаваемой корутины.
Когда функция launch вызывается без параметров, она перенимает контекст, в котором она создается и запускается. Например, используем метод Thread.currentThread(), который предоставляет JDK, чтобы получить данные потока корутины:
import kotlinx.coroutines.* suspend fun main() = coroutineScope{ launch { println("Корутина выполняется на потоке: ${Thread.currentThread().name}") } println("Функция main выполняется на потоке: ${Thread.currentThread().name}") }
Здесь с помощью переменной Thread.currentThread().name
мы можем получить имя потока. И в данном случае мы получим консольный вывод наподобие следующего:
Функция main выполняется на потоке: main Корутина выполняется на потоке: DefaultDispatcher-worker-1
Мы видим, что функция main
выполняется на потоке под названием "main" (который собственно и отведен для выполнения этой функции), а
корутина выполняется на другом потоке с названием DefaultDispatcher-worker-1
. Если мы обратимся к отладчику корутин, то мы сможем увидеть применяемый корутиной диспетчер:
Здесь мы видим, что корутина, которая выполняется в потоке "DefaultDispatcher-worker-1", применяет диспетчер Dispatcher.Default.
Поскольку контекст корутин в функции main
создается в данном случае с помощью функции coroutineScope, которая устанавливает
для создаваемых корутин по умолчанию диспетчер типа Dispatcher.Default. И, корутина, определенная в примере выше перенимает этот контекст вместе с
данным типом диспетчера.
Что это значит? Рассмотрим доступные типы диспетчеров:
Dispatchers.Default: применяется по умолчанию, если тип диспетчера не указан явным образом. Этот тип использует общий пул разделяемых фоновых потоков и подходит для вычислений, которые не работают с операциями ввода-вывода (операциями с файлами, базами данных, сетью) и которые требуют интенсивного потребления ресурсов центрального процессора.
Dispatchers.IO: использует общий пул потоков, создаваемых по мере необходимости, и предназначен для выполнения операций ввода-вывода (например, операции с файлами или сетевыми запросами).
Dispatchers.Main: применяется в графических приложениях, например, в приложениях Android или JavaFX.
Dispatchers.Unconfined: корутина не закреплена четко за определенным потоком или пулом потоков. Она запускается в текущем потоке до первой приостановки. После возобновления работы корутина продолжает работу в одном из потоков, который сторого не фиксирован. Разработчики языка Kotlin в обычной ситуации не рекомендуют использовать данный тип.
newSingleThreadContext и newFixedThreadPoolContext: позволяют вручную задать поток/пул для выполнения корутины
И мы можем сами задать для корутины диспетчер, передав в функцию launch
(а также async
) соответствующее значение:
import kotlinx.coroutines.* suspend fun main() = coroutineScope{ launch(Dispatchers.Default) { // явным образом определяем диспетчер Dispatcher.Default println("Корутина выполняется на потоке: ${Thread.currentThread().name}") } println("Функция main выполняется на потоке: ${Thread.currentThread().name}") }
Тип Dispatchers.Unconfined
запускает корутину в текущем вызывающем потоке до первой приостановки. После возобновления корутина продолжает работу в
одном из потоков, который строго не фиксирован. Подобный тип подходит для корутин, которым не требуется интенсивно потреблять время CPU или работать с общими данными,
наподобие объектов пользовательского интерфейса. Применим данный тип:
import kotlinx.coroutines.* suspend fun main() = coroutineScope{ launch(Dispatchers.Unconfined) { println("Поток корутины (до остановки): ${Thread.currentThread().name}") delay(500L) println("Поток корутины (после остановки): ${Thread.currentThread().name}") } println("Поток функции main: ${Thread.currentThread().name}") }
Консольный вывод:
Поток корутины (до остановки): main Поток функции main: main Поток корутины (после остановки): kotlinx.coroutines.DefaultExecutor
newSingleThreadContext вручную запускает поток с указанным именем:
import kotlinx.coroutines.* suspend fun main() = coroutineScope{ launch(newSingleThreadContext("Custom Thread")) { println("Поток корутины: ${Thread.currentThread().name}") } println("Поток функции main: ${Thread.currentThread().name}") }
В данном случае для выполнения корутины будет запускаться поток с именем "Custom Thread". Консольный вывод:
Поток функции main: main Поток корутины: Custom Thread
В то же время выделенный поток является довольно затратным ресурсом. И в реальном приложении подобый поток следует либо освобождать с помощью функции close(), если он больше не нужен, либо хранить в глобальной переменной и использовать его повторно для подобных задач на протяжении работы приложения.