Представим некого Петю, который недавно начал изучать корутины:
Вот он создал свой первый СoroutineScope
val coroutineScope = CoroutineScope(Job())
![](https://habrastorage.org/getpro/habr/upload_files/ef5/333/16f/ef533316f3c47fc4ee11384e4cf91c25.png)
Затем добавил launch
fun main() {
val coroutineScope = CoroutineScope(Job())
coroutineScope.launch {
println("my awesome coroutine")
}
}
Запускает все это в надежде увидеть:
my awesome coroutine
Process finished with exit code 0
а получает:
Process finished with exit code 0
![](https://habrastorage.org/getpro/habr/upload_files/b23/ce1/976/b23ce19764e9a8bf40b8d39506f8007f.png)
Прочитав данную статью, Петя поймет несколько важных вещей по работе с корутинами. Они помогут как в разработке, так и в прохождении собеседований!
Перейдем к самому вопросу:
Что выведет данный код?
fun main() {
val coroutineScope = CoroutineScope(Job())
coroutineScope.launch {
delay(1000) //имитация какой-то работы. Просто ждем одну секунду
println("Зеленое измерение")
}
println("Красное измерение")
}
Ответ на вопрос. Не открывай раньше времени, подумай сначала сам
Если ты ответил:
Зеленое измерение
Красное измерение
Process finished with exit code 0
Поздравляю!
![](https://habrastorage.org/getpro/habr/upload_files/cac/a48/836/caca488366d1809f75cb91534516beb1.jpg)
Твоя психика еще цела и невредима, но ответ неправильный :(
Выведется:
Красное измерение
Process finished with exit code 0
![](https://habrastorage.org/getpro/habr/upload_files/3c8/07b/ef8/3c807bef84997fcc7c29f23c1a1bfc30.png)
Если не знаешь, почему так, то переходи к объяснению ниже*
Объяснение. Открывай, когда узнал правильный ответ
Представь, что красная область внутри функции main() и зеленая область внутри launch — это д ва разных измерения.
Кстати, код внутри launch – это и есть "корутина". Далее я буду называть launch таким образом
![](https://habrastorage.org/getpro/habr/upload_files/e7b/d01/98c/e7bd0198ca3a6ae044e68409b4d925c8.png)
Давай узнаем разницу между измерениями:
Красное — это основное измерение (основной поток программы)
Зеленое — измерение, где временные задержки (delay) и последовательность выполнения кода могут отличаться от основного потока программы (красного измерения)
При запуске функции main() сначала красное измерение начинает выполнение, в то время как корутины могут начать свою работу параллельно или после запуска красного
Что в итоге происходит в нашем коде:
Когда мы запускаем main(), основной поток выполняет println(“Красное измерение”), не дожидаясь delay() в корутине, и программа завершается с фразой Process finished with exit code 0
Но как напечатать сначала “Зеленое измерение”, а потом “Красное измерение"?
Нам нужно сказать программе: "подожди пока выполнится delay(1000), а только потом напечатай "красное измерение"
Для этих целей можем использовать функцию join()
Добавим ее:
![](https://habrastorage.org/getpro/habr/upload_files/44d/d44/baa/44dd44baad4ed2cd76e9b09f49c86599.png)
join() приостанавливает красное измерение, пока зеленое не завершит delay()
Видим ошибку:
![](https://habrastorage.org/getpro/habr/upload_files/bd9/a31/d1f/bd9a31d1f6ab4a3dd8a774fff18b20df.png)
Нам предлагают добавить к функции main() слово suspend
suspend означает, что функция может приостановить свое выполнение
Обращу внимание на то, что приостанавливается функция main(), а корутина активна и выполняется
![](https://habrastorage.org/getpro/habr/upload_files/ed4/a55/8ee/ed4a558ee53778eb10171308353113bf.png)
Итак, сейчас красное измерение будет приостановлено
![](https://habrastorage.org/getpro/habr/upload_files/9f6/980/734/9f69807342dd8a5173723be7d5ddd361.png)
Добавив join(), мы имеем:
![](https://habrastorage.org/getpro/habr/upload_files/ad1/0b5/a28/ad10b5a28e1a28c44de96f5009534f73.png)
А теперь вопрос на засыпку!
Представим, что нет возможности использовать join()
Как сделать так, чтобы снова выводилось:
Зеленое измерение
Красное измерение
![](https://habrastorage.org/getpro/habr/upload_files/941/dd1/601/941dd16017cd8ba7b6ad362c07f363e4.png)
Ответ на вопрос. Не открывай раньше времени, подумай сначала сам
Нужно перенести delay() в красное измерение
Теперь suspend main() не может пройти мимо delay() и дождется его выполнения
Что нужно запомнить:
suspend функции должны быть вызваны внутри корутин или других suspend функций
join() — это suspend функция. В нашем случае она приостанавливает другую suspend функцию (а именно main)
suspend функции выделены специальным значком
![](https://habrastorage.org/getpro/habr/upload_files/484/98a/f24/48498af2417c41527a46ba01705bc234.png)
4. Корутины запускаются не мгновенно, на это требуется какое-то время
Еще небольшие задачки:
Как думаешь, что выведет этот код:
Ответ проверь в IDEA или напиши в комментарии :)
![Задача 1 Задача 1](https://habrastorage.org/getpro/habr/upload_files/38a/b04/58f/38ab0458f572bdbcf1604f9c4cdfd389.png)
![Задача 2 Задача 2](https://habrastorage.org/getpro/habr/upload_files/d0a/6c4/cb7/d0a6c4cb75898b77eb9a1d2ed9f59a62.png)
![Задача 3 Задача 3](https://habrastorage.org/getpro/habr/upload_files/fca/277/20b/fca27720b10cf68c7a8dbed246a59563.png)
Комментарии (9)
Rusrst
31.03.2024 12:18+1Осталось узнать про main.immediate и пример без delay будет иметь иное поведение. А ещё с coroutine start поиграться. Но вообще странно это приводить как пример, это вроде описано.
upcFrost
Я не андроид-разработчик, но разве корутины в котлине не следуют обычному cooperative multitasking, когда грубо говоря есть системный цикл который по очереди проходит по всем scheduled задачам (корутинам) и выполняет их насколько это возможно (если нет io, если прошёл delay, если завершилась отрисовка и т.д.)? Ну и соответственно пока не произойдёт переключение контекста (выход из контекста текущей задачи в системный цикл) - корутина не запустится.
imanushin
Да, всё так, вот только есть несколько (независимых и не очень) системных циклов, которые делают ровно то, что Вы описали. По сути, любая корутина будет выполняться в одном из Dispatcher'ов.
upcFrost
Тогда не проще ли думать/объяснять корутины именно в контексте этих циклов? Ну, условно что join выходит из контекста задачи обратно у цикл, и задача не пойдёт в выполнение (будет пропущена в цикле) до тех пор пока не завершится ожидание помеченной join корутины. И ради иллюстраций взять обычную ladder diagram.
Кмк это намного проще для понимания чем "измерения" или "некоторое время на запуск"
dankeshon_1 Автор
Спасибо, подумаю над этим) Рассказывать просто о сложных вещах - это искусство, буду тренироваться)
upcFrost
Я джунам именно про цикл рассказывал, туда очень органично легли всякие sleep(0), опасность блокировки цикла cpu-bound задачами, очереди, порядок выполнения и прочее. Но я питонист, в питоне судя по другим комментам проще чем в котлине, по умолчанию loop только один.
zagayevskiy
В сабжевом коде нет системного цикла, завершается main, завершается процесс. Корутина не начинает выполняться.
Для начала работы корутины не обязательно завершение текущей задачи. Она может хоть навечно повиснуть, если в диспатчере будут свободные потоки, корутина запустится на одном из них.
ТСа хочется с подключением поздравить:)
upcFrost
И свой цикл есть у каждого потока и пул задач между ними общий?
zagayevskiy
По-умолчанию в JVM у "сырых" потоков нет цикла. В примере есть поток, на котором выполняется main, в нем, соответственно, нет цикла.
Корутина шедулится на дефолтный диспетчер, в нём пул потоков, в каждом потоке цикл, пул задач общий, тут всё как ты сказал.
Если всобачить перед окончанием main Thread.sleep(1000), то код корутины успеет выполниться.