Представим некого Петю, который недавно начал изучать корутины:

Вот он создал свой первый СoroutineScope

val coroutineScope = CoroutineScope(Job())

Затем добавил 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

Прочитав данную статью, Петя поймет несколько важных вещей по работе с корутинами. Они помогут как в разработке, так и в прохождении собеседований!

Перейдем к самому вопросу:

Что выведет данный код?

fun main() {
    val coroutineScope = CoroutineScope(Job())

    coroutineScope.launch {
        delay(1000) //имитация какой-то работы. Просто ждем одну секунду
        println("Зеленое измерение")
    }

    println("Красное измерение")
}
Ответ на вопрос. Не открывай раньше времени, подумай сначала сам

Если ты ответил:

Зеленое измерение
Красное измерение

Process finished with exit code 0

Поздравляю!

Твоя психика еще цела и невредима, но ответ неправильный :(

Выведется:

Красное измерение

Process finished with exit code 0

Если не знаешь, почему так, то переходи к объяснению ниже*

Объяснение. Открывай, когда узнал правильный ответ

Представь, что красная область внутри функции main() и зеленая область внутри launch — это д ва разных измерения.
Кстати, код внутри launch – это и есть "корутина". Далее я буду называть launch таким образом

Давай узнаем разницу между измерениями:

  • Красное — это основное измерение (основной поток программы)

  • Зеленое — измерение, где временные задержки (delay) и последовательность выполнения кода могут отличаться от основного потока программы (красного измерения)

При запуске функции main() сначала красное измерение начинает выполнение, в то время как корутины могут начать свою работу параллельно или после запуска красного

Что в итоге происходит в нашем коде:

Когда мы запускаем main(), основной поток выполняет println(“Красное измерение”), не дожидаясь delay() в корутине, и программа завершается с фразой Process finished with exit code 0

Но как напечатать сначала “Зеленое измерение”, а потом “Красное измерение"?

Нам нужно сказать программе: "подожди пока выполнится delay(1000), а только потом напечатай "красное измерение"

Для этих целей можем использовать функцию join()

Добавим ее:

join() приостанавливает красное измерение, пока зеленое не завершит delay()

Видим ошибку:

Нам предлагают добавить к функции main() слово suspend

suspend
означает, что функция может приостановить свое выполнение
Обращу внимание на то, что приостанавливается функция main(), а корутина активна и выполняется

Итак, сейчас красное измерение будет приостановлено

Добавив join(), мы имеем:

А теперь вопрос на засыпку!

Представим, что нет возможности использовать join()

Как сделать так, чтобы снова выводилось:
Зеленое измерение
Красное измерение

Ответ на вопрос. Не открывай раньше времени, подумай сначала сам

Нужно перенести delay() в красное измерение

Теперь suspend main() не может пройти мимо delay() и дождется его выполнения

Что нужно запомнить:

  1. suspend функции должны быть вызваны внутри корутин или других suspend функций

  2. join() — это suspend функция. В нашем случае она приостанавливает другую suspend функцию (а именно main)

  3. suspend функции выделены специальным значком

4. Корутины запускаются не мгновенно, на это требуется какое-то время

Еще небольшие задачки:

Как думаешь, что выведет этот код:
Ответ проверь в IDEA или напиши в комментарии :)

Задача 1
Задача 1
Задача 2
Задача 2
Задача 3
Задача 3

Комментарии (9)


  1. upcFrost
    31.03.2024 12:18
    +1

    Я не андроид-разработчик, но разве корутины в котлине не следуют обычному cooperative multitasking, когда грубо говоря есть системный цикл который по очереди проходит по всем scheduled задачам (корутинам) и выполняет их насколько это возможно (если нет io, если прошёл delay, если завершилась отрисовка и т.д.)? Ну и соответственно пока не произойдёт переключение контекста (выход из контекста текущей задачи в системный цикл) - корутина не запустится.


    1. imanushin
      31.03.2024 12:18

      Да, всё так, вот только есть несколько (независимых и не очень) системных циклов, которые делают ровно то, что Вы описали. По сути, любая корутина будет выполняться в одном из Dispatcher'ов.


      1. upcFrost
        31.03.2024 12:18

        Тогда не проще ли думать/объяснять корутины именно в контексте этих циклов? Ну, условно что join выходит из контекста задачи обратно у цикл, и задача не пойдёт в выполнение (будет пропущена в цикле) до тех пор пока не завершится ожидание помеченной join корутины. И ради иллюстраций взять обычную ladder diagram.

        Кмк это намного проще для понимания чем "измерения" или "некоторое время на запуск"


        1. dankeshon_1 Автор
          31.03.2024 12:18

          Спасибо, подумаю над этим) Рассказывать просто о сложных вещах - это искусство, буду тренироваться)


          1. upcFrost
            31.03.2024 12:18
            +1

            Я джунам именно про цикл рассказывал, туда очень органично легли всякие sleep(0), опасность блокировки цикла cpu-bound задачами, очереди, порядок выполнения и прочее. Но я питонист, в питоне судя по другим комментам проще чем в котлине, по умолчанию loop только один.


    1. zagayevskiy
      31.03.2024 12:18
      +1

      В сабжевом коде нет системного цикла, завершается main, завершается процесс. Корутина не начинает выполняться.

      Для начала работы корутины не обязательно завершение текущей задачи. Она может хоть навечно повиснуть, если в диспатчере будут свободные потоки, корутина запустится на одном из них.

      ТСа хочется с подключением поздравить:)


      1. upcFrost
        31.03.2024 12:18

        И свой цикл есть у каждого потока и пул задач между ними общий?


        1. zagayevskiy
          31.03.2024 12:18

          По-умолчанию в JVM у "сырых" потоков нет цикла. В примере есть поток, на котором выполняется main, в нем, соответственно, нет цикла.

          Корутина шедулится на дефолтный диспетчер, в нём пул потоков, в каждом потоке цикл, пул задач общий, тут всё как ты сказал.

          Если всобачить перед окончанием main Thread.sleep(1000), то код корутины успеет выполниться.


  1. Rusrst
    31.03.2024 12:18
    +1

    Осталось узнать про main.immediate и пример без delay будет иметь иное поведение. А ещё с coroutine start поиграться. Но вообще странно это приводить как пример, это вроде описано.