Я читал Основы сопрограмм, пытаясь понять и изучить его.
Там есть часть с этим кодом:
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
....
}
приостановлено?
Там же есть примечание:
The main difference between runBlocking and coroutineScope is that the latter does not block the current thread while waiting for all children to complete.
Я не понимаю, чем здесь отличаются coroutineScope и runBlocking? coroutineScope выглядит так, как будто его блокирует, поскольку он доходит только до последней строки, когда это сделано.
Может ли кто-нибудь просветить меня здесь?
Заранее спасибо.
Следует обновить документацию, чтобы лучше объяснить это, потому что вы не единственный, кто интерпретировал это таким образом.
У Kotlin такая ужасная документация, он постоянно выкидывает концепции из ниоткуда и сбивает с толку объяснения.





runBlocking просто блоки текущий поток, пока внутренние сопрограммы не будут завершены. Здесь поток, выполняющий runBlocking, будет заблокирован до тех пор, пока, сопрограмма из coroutineScope будет завершена.
Первый launch просто не позволит потоку выполнять инструкции, которые идут после runBlocking, но позволит перейти к инструкциям, которые идут сразу после этого блока launch - поэтому Task from coroutine scope печатается раньше, чем Task from runBlocking.
Но вложенный coroutineScope в контексте runBlocking не позволяет потоку выполнять инструкции, которые идут после этого блока coroutineScope, потому что runBlocking блокирует поток до тех пор, пока сопрограмма из coroutineScope не будет завершена полностью. Вот почему Coroutine scope is over всегда будет следовать за Task from nested launch.
I don't understand how coroutineScope and runBlocking are different here? coroutineScope looks like its blocking since it only gets to the last line when it is done.
С точки зрения кода в блоке ваше понимание правильное. Разница между runBlocking и coroutineScope происходит на более низком уровне: что происходит с потоком, когда сопрограмма заблокирована?
runBlocking не является suspend fun. Вызвавший его поток остается внутри него до завершения сопрограммы.
coroutineScope - это suspend fun. Если ваша сопрограмма приостанавливается, функция coroutineScope также приостанавливается. Это позволяет функции верхнего уровня, функции без приостановки, создавшей сопрограмму, продолжать выполнение в том же потоке. Поток «экранировал» блок coroutineScope и готов выполнить другую работу.
В вашем конкретном примере: когда ваш coroutineScope приостанавливается, управление возвращается коду реализации внутри runBlocking. Этот код представляет собой цикл событий, который запускает все сопрограммы, которые вы запустили в нем. В вашем случае некоторые сопрограммы будут запускаться после задержки. Когда придет время, он возобновит соответствующую сопрограмму, которая будет работать на короткое время, приостановится, а затем управление снова будет внутри runBlocking.
Хотя приведенное выше описывает концептуальное сходство, оно также должно показать вам, что runBlocking - это инструмент полностью отличается от coroutineScope.
runBlocking - это низкоуровневая конструкция, которая должна использоваться только в коде фреймворка или автономных примерах, подобных вашему. Он превращает существующий поток в цикл событий и создает свою сопрограмму с Dispatcher, которая отправляет возобновляющие сопрограммы в очередь цикла событий.
coroutineScope - это обращенная к пользователю конструкция, используемая для определения границ задачи, которая распараллеливается внутри нее. Вы используете его для удобного ожидания всей работы async, происходящей внутри него, получения окончательного результата и обработки всех сбоев в одном центре.
Это только из-за runBlocking, что последняя строка вызывается после завершения coroutineScope?
Нет. Два разных мира - это мир подвешиваемый (внутри сопрограммы) и мир не приостанавливаемый. Как только вы входите в тело runBlocking, вы попадаете в подвешиваемый мир, где suspend fun ведут себя как блокирующий код, и вы не можете перейти к следующей строке, пока suspend fun не вернется. coroutineScope - это suspend fun, который возвращается только тогда, когда все сопрограммы внутри него выполнены. Следовательно, последняя строка должна печататься в конце.
Наконец-то я понял! Большое тебе спасибо! Я путал CoroutineScope с coroutineScope. Теперь я понимаю, что это две разные вещи. CoroutineScope запускает сопрограмму, в то время как coroutineScope - это функция приостановки, которая связывает построение сопрограмм и функцию приостановки :) Большое вам спасибо :)
CoroutineScope - это просто держатель CoroutineContext, вот и все. launch, async и другие - это расширения для CoroutineScope, они просто извлекают его контекст, комбинируют его с любым контекстом, который вы дополнительно указали в аргументе, и в результате получается контекст сопрограммы, которую вы создаете.
Отличный ответ! Я могу спросить об этой линии "to continue executing on the same thread". Должен ли быть тот же поток для продолжения выполнения?
@stdout В этом предложении говорится о механизме нижнего уровня. То, что продолжает выполнение, - это код цикла событий верхнего уровня, а не какая-либо сопрограмма. Затем этот код выберет другую сопрограмму, отправленную в тот же поток, и возобновит ее.
Из этой замечательной статьи 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()
}
With runBlocking, we were not using Structured Concurrency, so an invocation of f could fail and all other executions would continue unfazed. And also we were not playing nice with the rest of the code. By using runBlocking we were forcefully blocking the thread until the whole execution of pmap finishes, instead of letting the caller decide how the execution should go.
Выбранный ответ хорош, но не затрагивает некоторые другие важные аспекты предоставленного примера кода. Например, запуск не блокируется и должен выполняться немедленно. Это просто не соответствует действительности. Сам запуск немедленно возвращается, НО код внутри запуска кажется помещенным в очередь и выполняется только тогда, когда все другие запуски, которые ранее были помещены в очередь, завершены.
Вот аналогичный пример кода со всеми удаленными задержками и включенным дополнительным запуском. Не глядя на результат ниже, посмотрите, сможете ли вы предсказать порядок, в котором будут напечатаны числа. Скорее всего, у вас ничего не получится:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
coroutineScope {
launch {
println("2")
}
println("3")
}
coroutineScope {
launch {
println("4")
}
println("5")
}
launch {
println("6")
}
for (i in 7..100) {
println(i.toString())
}
println("101")
}
Результат:
3
1
2
5
4
7
8
9
10
...
99
100
101
6
Тот факт, что номер 6 печатается последним, даже после того, как было выполнено почти 100 println, указывает на то, что код внутри последнего запуска никогда не выполняется, пока не будет выполнен весь неблокирующий код после запуска. Но это тоже не совсем так, потому что, если бы это было так, первый запуск не должен был бы выполняться до тех пор, пока не будут выполнены номера с 7 по 101. Нижняя линия? Смешивание запуска и coroutineScope в высшей степени непредсказуемо, и его следует избегать, если вы ожидаете определенного порядка в том, как все должно выполняться.
Чтобы доказать, что код внутри запусков помещается в очередь и выполняется ТОЛЬКО после того, как ВСЕ неблокирующий код завершен, запустите это (никакие сопрограммы не используются):
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
launch {
println("2")
}
launch {
println("3")
}
for (i in 4..100) {
println(i.toString())
}
println("101")
}
Вот результат, который вы получите:
4
5
6
...
101
1
2
3
Добавление CoroutineScope нарушит это поведение. Это приведет к тому, что весь неблокирующий код, следующий за CoroutineScope, не будет выполняться до тех пор, пока ВСЕ код до CoroutineScope не будет завершен.
Следует также отметить, что в этом примере кода каждый запуск в очереди выполняется последовательно в том порядке, в котором они добавляются в очередь, и каждый запуск будет выполняться только ПОСЛЕ выполнения предыдущего запуска. Это может создать впечатление, что все запуски имеют общую нить. Это неправда. Каждому из них дается собственная ветка. Однако, если какой-либо код внутри запуска вызывает функцию приостановки, следующий запуск в очереди запускается немедленно, пока выполняется функция приостановки. Если честно, это очень странное поведение. Почему бы просто не запускать все запуски в очереди асинхронно? Хотя я не знаю, что происходит в этой очереди, я предполагаю, что каждый запуск в очереди не имеет собственного потока, но все они используют общий поток. Только когда встречается функция приостановки, кажется, что создается новый поток для следующего запуска в очереди. Это можно сделать так, чтобы сэкономить ресурсы.
Подводя итог, выполнение выполняется в следующем порядке:
во-первых, есть разница между CoroutineScope и coroutineScope, так что будьте осторожны с этим.
Причина, по которой coroutineScope «блокирует» следующий за ним код, заключается в том, что coroutineScope является функцией приостановки. coroutineScope приостанавливается до тех пор, пока все программы / функции не вернутся. По сути, это механизм «обзора».
неблокирующий код (launch) возвращается немедленно, поэтому код рядом с ним выполняется немедленно.
Я не уверен в этом, но считаю, что в сопрограммах нет концепции «очереди». Если сопрограммы обрабатываются в «очереди», то не должно быть возможности, чтобы вторая запущенная сопрограмма без задержки выполняла свою работу первой над первой сопрограммой luanch с задержкой в 2 секунды.
В целом, хотя я думаю, что объяснение довольно хорошее, я все равно буду его голосовать.
@ ArchieG.Quiñones Неважно, используется ли очередь внутренне или нет. Он определенно действует как единое целое. Чтобы проверить это, просто запустите один за другим запуски, и пусть они оба проделают значительный объем работы. Второй запуск не начнет выполнение своего кода, пока не завершится первый. Добавьте третий запуск, и он не начнется, пока не завершится второй. Но, как уже упоминалось, это поведение изменяется в тот момент, когда какой-либо код в сопрограмме вызывает функцию приостановки.
В документации также говорится: «Обратите внимание, что параллелизм с сопрограммами всегда явный». - это означает, что они выполняются последовательно, если вы явно не запрашиваете их асинхронное выполнение - отсюда и понятие «очереди».
У меня действительно проблема с тем, чтобы рассматривать сопрограммы как очередь. На самом деле это больше похоже на стек.
The second launch will not start executing its code until the first one completes --- это просто артефакт использования диспетчера однопоточных сопрограмм и не принадлежит семантике сопрограмм. Семантика такова: «одновременное выполнение, которое начинается немедленно при вызове launch», а остальное остается на усмотрение внутреннего планирования.
runBlocking предназначен для блокировки основного потока.
coroutineScope предназначен для блокировки runBlocking.
Вау, это так просто! ?
Итак, прочитав здесь все ответы, я не обнаружил, что ни один из них не ответил на вопрос, кроме повторения формулировок фрагментов документации.
Итак, я продолжил поиск ответа в другом месте и нашел его здесь. Практически показывает разницу в поведении coroutineScope и runBlocking (то есть разницу между приостановкой и блокировкой).
вы также можете найти документацию по
coroutineScopeпроницательной ....