Dispatchers.Main под капотом

Dispatchers.Main под капотом

Dmitry Tsyvtsyn

CoroutineDispatcher - один из базовых компонентов Kotlin корутин, предназначенный для переключения потоков, чтобы разобраться как он работает под капотом, немного погрузимся в общий принцип работы корутин.

Любая корутина создаёт Continuation объект и передаёт его suspend функциям, те в свою очередь вызывают метод continuation.resumeWith() когда нужно вернуть результат или ошибку, это можно сравнить с обычными callback'ами, только с тем отличием, что в Kotlin Coroutines всё реализовано под капотом + есть дополнительные механизмы в стиле Structured Concurrency.

Но чтобы переключать потоки знания общего принципа достаточно, давайте задумаемся на минутку, что надо сделать, чтобы результат callback'а или Continuation'а в нашем случае был на другом потоке? 

Да конечно вызвать Continuation.resumeWith() на другом потоке:


// IO thread
val ctx = coroutineContext
// метод isDispatchNeeded проверяет есть ли смысл переключать корутину на главный поток, чтобы не делать лишних переключений, если корутина и так находится на главном потоке
if (mainDispatcher.isDispatchNeeded(ctx)) {
// метод dispatch выполняет блок кода на главном потоке
mainDispatcher.dispatch(ctx) {
  // Main thread
  continuation.resumeWith(...)
}
} else {
  // уже находимся на главном потоке, продолжаем выполнение
continuation.resumeWith(...)
}


Если у вас появится желание написать свой CoroutineDispatcher, то вы должны обязательно реализовать метод dispatch() и было бы неплохо переопределить isDispatchNeeded() чтобы исключить лишние переключения.

Вернёмся к главному потоку, в Android такой Dispatcher или как он назван в исходниках HandlerContext реализован с помощью Handler'а:


// реализация для Dispatchers.Main
override fun isDispatchNeeded(...) = true

// реализация для Dispatchers.Main.immediate
override fun isDispatchNeeded(...): Boolean {
// сравнивает Looper текущего потока с главным
  // handler.looper это Looper.getMainLooper()
return Looper.myLooper() != handler.looper
}

override fun dispatch(
context: CoroutineContext, 
block: Runnable
) {
// handler.post() выполняет код на главном потоке
if (!handler.post(block)) { ... }
}


Dispatchers.Main не делает проверок на главный поток, поэтому если написать несколько вложенных viewModelScope.launch() вызовов всегда будет происходить переключение текущего потока на главный через Handler.post(), даже если код уже выполняется на главном потоке.

Чтобы избежать ненужных переключений используйте Dispatchers.Main.immediate, в таком случае если код уже выполняется на главном потоке, метод isDispatchNeeded() вернёт false и dispatch() не будет вызван.

P.S. Хочу порекомендовать крутой канал с авторским контентом и полезными материалами в области Android разработки @dolgo_polo_dev

Пишите в комментах ваше мнение и всем хорошего кода!

Report Page