Как CoroutineScope(job+Dispatchers.Main) может работать в основном потоке/потоке пользовательского интерфейса?

Если операции внутри CoroutineScope(job+Dispatchers.Main){...} выполняются в основном потоке, то почему это не нарушает требование Android о том, что медленные (блокирующие) операции (сеть и т. д.) не могут выполняться в основном потоке/потоке пользовательского интерфейса? Я могу запускать блокирующие операции с этой областью действия, и пользовательский интерфейс вообще не зависает.

Буду признателен, если кто-нибудь объяснит, что происходит под капотом. Я предполагаю, что это похоже на то, как JavaScript управляет операциями блокировки с циклом событий, но я изо всех сил пытаюсь найти какие-либо соответствующие материалы.


person 372    schedule 18.02.2020    source источник
comment
Что вы имеете в виду под I can run blocking operations with this scope and the UI does not freeze at all. ? Пожалуйста, приведите пример?   -  person Dmitrii Leonov    schedule 18.02.2020
comment
@DmitriiLeonov, например, я могу приостановить поток, вызвав delay(1000). Разве это не должно вызывать появление экрана ANR, если я делаю это в потоке пользовательского интерфейса?   -  person 372    schedule 18.02.2020


Ответы (2)


Я предполагаю, что это похоже на то, как JavaScript управляет операциями блокировки с циклом событий.

Да, это правильно, цикл событий необходим для работы сопрограмм. В основном, когда вы пишете это:

uiScope.launch {
    delay(1000)
    println("A second has passed")
}

он компилируется в код, который имеет тот же эффект, что и этот:

Handler(Looper.mainLooper()).postDelayed(1000) { println("A second has passed") }

Основной концепцией является продолжение, объект, который реализует конечный автомат, соответствующий последовательному коду, который вы написали в приостанавливаемой функции. Когда вы вызываете delay или любую другую приостанавливаемую функцию, метод точки входа продолжения возвращает специальное значение COROUTINE_SUSPENDED. Позже, когда какой-то внешний код получит возвращаемое значение функции suspendable, он должен будет вызвать continuation.resume(result). Этот вызов будет перехвачен ответственным диспетчером, который опубликует этот вызов как событие в цикле событий графического интерфейса. Когда обработчик события удаляется из очереди и выполняется, вы снова оказываетесь внутри конечного автомата, который определяет, где возобновить выполнение.

Вы можете просмотреть этот ответ для более подробной информации. конкретизированный пример использования Continuation API.

person Marko Topolnik    schedule 20.02.2020
comment
Спасибо за объяснение. Я не видел, чтобы кто-нибудь предоставил эквивалентный пример на основе Handler. - person 372; 21.02.2020
comment
Функция delay является неблокирующей. ОП спрашивал об операции блокировки, например. некоторая логика обработки изображений. - person alekop; 25.05.2021
comment
@alekop ОП: я могу выполнять блокирующие операции с этой областью, и пользовательский интерфейс вообще не зависает. например, я могу приостановить поток, вызвав delay(1000). Это означает, что они используют слово «блокировка» в более общем смысле, чем в обычном жаргоне. В этом смысле оно включает в себя и приостановку поведения. - person Marko Topolnik; 25.05.2021

Запуск операций блокировки и запуск операций приостановки на CoroutineScope(Dispatchers.Main) — это две разные вещи.

delay() — это функция приостановки, и она не блокируется.

CoroutineScope(Dispatchers.Main){
    delay(6000)
}

Пока Thread.sleep() блокируется, вызов кода ниже вызовет ANR

CoroutineScope(Dispatchers.Main){
    Thread.sleep(6000)
}

Я предлагаю вам проверить выступление Романа Елизарова о сопрограммах Kotlin на Kotlinconf 2017, особенно ту часть, где он запускает 100 000 delay()

person Dmitrii Leonov    schedule 19.02.2020
comment
Спасибо за понимание! - person 372; 21.02.2020