Сопрограммы: runBlocking против coroutineScope
Я читал основы Coroutine, пытаясь понять и изучить это.
Там есть часть с этим кодом:
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // Creates a new coroutine scope
launch {
delay(900L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope") // This line will be printed before nested launch
}
println("Coroutine scope is over") // This line is not printed until nested launch completes
}
Вывод выглядит так:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
Мой вопрос, почему эта строка:
println("Coroutine scope is over") // This line is not printed until nested launch completes
называется всегда последний?
Не должен ли он быть вызван, так как:
coroutineScope { // Creates a new coroutine scope
....
}
приостановлено?
Там также есть примечание:
Основное различие между runBlocking и coroutineScope заключается в том, что последний не блокирует текущий поток, ожидая завершения всех дочерних элементов.
Я не понимаю, чем здесь отличаются coroutineScope и runBlocking? coroutineScope выглядит как его блокировка, так как он достигает только последней строки, когда это сделано.
Кто-нибудь может просветить меня здесь?
Заранее спасибо.
Ответы
Ответ 1
Я не понимаю, чем отличаются coroutineScope и runBlocking? coroutineScope выглядит как его блокировка, так как он достигает только последней строки, когда это сделано.
С точки зрения кода в блоке, ваше понимание верно. Разница между runBlocking
и coroutineScope
происходит на более низком уровне: что происходит с потоком, пока сопрограмма блокируется?
-
runBlocking
- это не suspend fun
. Нить, вызвавшая его, остается внутри, пока сопрограмма не будет завершена.
-
coroutineScope
это suspend fun
. Если ваша сопрограмма приостанавливается, функция coroutineScope
приостанавливается. Это позволяет функции верхнего уровня, не приостанавливающей функцию, которая создала сопрограмму, продолжить выполнение в том же потоке. Поток "вышел" из блока coroutineScope
и готов выполнить другую работу.
В вашем конкретном примере: когда ваш coroutineScope
приостанавливается, элемент управления возвращается к коду реализации внутри runBlocking
. Этот код представляет собой цикл обработки событий, который управляет всеми сопрограммами, которые вы начали в нем. В вашем случае будут некоторые сопрограммы, запланированные для запуска после задержки. Когда время придет, оно возобновит соответствующую сопрограмму, которая будет работать в течение короткого времени, приостанавливается, и затем управление снова будет внутри runBlocking
.
Хотя вышеизложенное описывает концептуальное сходство, оно также должно показать вам, что runBlocking
- это совершенно другой инструмент, чем coroutineScope
.
-
runBlocking
- это низкоуровневая конструкция, предназначенная для использования только в коде фреймворка или автономных примерах, подобных вашему. Он превращает существующий поток в цикл событий и создает его сопрограмму с Dispatcher
который отправляет возобновляемые сопрограммы в очередь цикла событий.
-
coroutineScope
- это ориентированная на пользователя конструкция, используемая для определения границ задачи, которая распадается внутри нее параллельно. Вы используете его для удобного ожидания всей async
работы, происходящей внутри него, получения окончательного результата и обработки всех сбоев в одном центральном месте.
Ответ 2
runBlocking
просто блокирует текущий поток, пока внутренние сопрограммы не будут завершены. Здесь поток, выполняющий runBlocking
будет заблокирован, пока сопрограмма из coroutineScope
не будет завершена.
Первый launch
просто не позволит потоку выполнить инструкции, которые идут после runBlocking
, но позволит перейти к инструкциям, которые идут сразу после этого блока launch
- именно поэтому Task from coroutine scope
Task from runBlocking
раньше, чем Task from runBlocking
.
Но вложенный coroutineScope
в контексте runBlocking
не позволит потоку выполнять инструкции, которые следуют после этого блока coroutineScope
, потому что runBlocking
будет блокировать поток до тех пор, пока сопрограмма из coroutineScope
не будет полностью завершена. И вот почему Coroutine scope is over
, всегда будет приходить после Task from nested launch
.
Ответ 3
Из этой замечательной статьи https://jivimberg.io/blog/2018/05/04/parallel-map-in-kotlin/
suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> = coroutineScope {
map { async { f(it) } }.awaitAll()
}
При использовании runBlocking мы не использовали структурированный параллелизм, поэтому вызов f может завершиться сбоем, и все остальные выполнения останутся неизменными. А также мы не очень хорошо играли с остальным кодом. Используя runBlocking, мы принудительно блокировали поток до тех пор, пока не закончится все выполнение pmap, вместо того, чтобы позволить вызывающей стороне решить, как должно выполняться выполнение.