Coroutines basics
This section covers basic coroutine concepts.
Your first coroutine
A coroutine is an instance of suspendable computation. It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code. However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.
Coroutines can be thought of as light-weight threads, but there is a number of important differences that make their real-life usage very different from threads.
Run the following code to get to your first working coroutine:
You can get the full code here.
You will see the following result:
Let’s dissect what this code does.
launch is a coroutine builder. It launches a new coroutine concurrently with the rest of the code, which continues to work independently. That’s why Hello has been printed first.
delay is a special suspending function. It suspends the coroutine for a specific time. Suspending a coroutine does not block the underlying thread, but allows other coroutines to run and use the underlying thread for their code.
If you remove or forget runBlocking in this code, you’ll get an error on the launch call, since launch is declared only in the CoroutineScope:
Structured concurrency
Coroutines follow a principle of structured concurrency which means that new coroutines can be only launched in a specific CoroutineScope which delimits the lifetime of the coroutine. The above example shows that runBlocking establishes the corresponding scope and that is why the previous example waits until World! is printed after a second’s delay and only then exits.
In a real application, you will be launching a lot of coroutines. Structured concurrency ensures that they are not lost and do not leak. An outer scope cannot complete until all its children coroutines complete. Structured concurrency also ensures that any errors in the code are properly reported and are never lost.
Extract function refactoring
You can get the full code here.
Scope builder
In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the coroutineScope builder. It creates a coroutine scope and does not complete until all launched children complete.
runBlocking and coroutineScope builders may look similar because they both wait for their body and all its children to complete. The main difference is that the runBlocking method blocks the current thread for waiting, while coroutineScope just suspends, releasing the underlying thread for other usages. Because of that difference, runBlocking is a regular function and coroutineScope is a suspending function.
You can use coroutineScope from any suspending function. For example, you can move the concurrent printing of Hello and World into a suspend fun doWorld() function:
You can get the full code here.
This code also prints:
Scope builder and concurrency
A coroutineScope builder can be used inside any suspending function to perform multiple concurrent operations. Let’s launch two concurrent coroutines inside a doWorld suspending function:
You can get the full code here.
An explicit job
A launch coroutine builder returns a Job object that is a handle to the launched coroutine and can be used to explicitly wait for its completion. For example, you can wait for completion of the child coroutine and then print «Done» string:
You can get the full code here.
This code produces:
Coroutines ARE light-weight
Run the following code:
You can get the full code here.
It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot.
Корутины в Kotlin. Основные понятия.
What is the scope? What is the difference between scopes?
Интерфейс CoroutineScope содержит внутри себя только CoroutineContext, причем GlobalScope содержит внутри себя EmptyCoroutineContext.
Ты можешь создать свой scope — например для Активити, все корутины создать на этом scope, и когда вызовется метод onDestroy() вызвать метод cancel() что завершит все корутины, созданные на этой области видимости. Таким образом ты избежишь утечек памяти и не придется убивать корутины вручную.
Почему стоит избегать GlobalScope?
Потому что если мы создаем корутину, и потом внутри нее еще одну с GlobalScope, мы нарушаем иерархию и родительская корутина не может завершить своих детей. Даже более того, родитель не видит своих детей и может завершиться раньше чем они закончат свою работу.
Что такое CoroutineContext?
По дефолту используется везде EmptyCoroutineContext, который содержится внутри GlobalScope. CoroutineContext предоставляет для корутины необходимую среду для выполнения. При создании новой корутины, происходит слияние родительского и дочернего CoroutineContext.
Что такое CoroutineDispatcher?
По сути это то же самое что Schedulers в RxJava. С помощью них, можно сменить выполняемый поток. Есть ряд диспетчеров: Default, Main, IO, Unconfined
Как запустить корутину из обычной функции?
Для этого есть набор coroutine builder-ов, типа runBlocking, launch, async, которые внутри себя содержат набор дефолтных параметров и в итоге вызывают внутри котлина функции suspend, которые хитрым образом с помощью continuation превращаются в switch case.
Как вообще корутины работают?
Самы корутины представляют собой Job — который отвечает за фоновую работу и у него есть свой жизненный цикл, дети и родители, также отвечает за отмену выполнения. Так же как у потоков, есть стандартный набор методов: start(), join(), cancel().
Как остановить корутины?
Каждая корутина по сути это Job, который реализует ряд стандартных методов, как и Java.Thread. Так что можно остановить корутину с помощью метода cancel().
Coroutine builder — служат для создания корутины. Основные типы билдеров:async, launch, withContext, runBlocking
Launch — создает новый поток в фоне. Называется launch потому что запустил и забыл. Не блокирует остальной код. Возвращает Job(позволяет отменить). Для того, чтобы обновить UI нужно переключить поток на UI — launch(Main)<>. Содержит внутри лямбды модификатор suspend, потому можно вызвать из обычной функции.
Async — возвращает результат(Future) в виде Deffered(T). Чтобы дождаться результата последовательно, есть функция async.await() — которая ждет пока закончит работу корутина. Причем, мы можем запустить 10 параллельных запросов в async() и дождаться результата их выполнения в другом места в result.await(). Функции suspend последовательны по умолчанию, если хочу параллельности использую async().
Концепция корутин
https://www.youtube.com/watch?v=yBxaE-qAPdI&ab_channel=MobileDeveloper
https://proandroiddev.com/understanding-kotlin-coroutines-with-this-mental-model-e1205e3f9670
Рутина — блок функций
Корутина может быть приостановлена и запущена
Все работают на главном потоке, без создания потоков
Абстракция над потоками. Хоть Thread и один, но worker разные.
Как же это работает?
Дело в том, что Котлин компилятор превращает suspend функцию в обычный код с switch case, и дополнительным параметром continuation(state машина), который и отвечает за то, какой участок кода должен выполниться.
Почему Thread.sleep() блокирует поток а delay() нет?
delay() для MainDisatcher использует внутри себя handler.postDelayed(resumeWorkBlock, delay). То есть просто ставит сообщение в очередь через некоторое время.

Но все таки, работают ли все корутины на одном потоке или все-таки на пуле потоков?
Если назначаем MainCoroutineDispatcher, то работа происходит на main thread. Если же на одном из других, то на одном из пулов (IO, Common(Default), Unconfined).
Про корутины в общем
В чем проблема Тредов? В том, что они вынуждают нас использовать колбэки, что порождает callback-hell. Все Фреймворк, типа RxJva, Promise, Future(например Call в ретрофите) это обертки для этих колбэков. То есть мы вместо передачи еще одного параметра, можем получить результат. Проблема в том, что эти Фреймворк требуют знания операторов и методов.
В Котлине вместо этого добавили модификатор suspend, который помечается IDE как асинхронный код. По сути, тот же колбэк continuation. Но он разгуливается на уровне компилятора. Причем, если мы пишем колбэки вручную, каждый раз создается новый объект.
Если нам нужно использовать Call из ретрофита, нужно убрать фьючерс Call, и оставить только Response.
Что такое Корутины в Котлине?
Корутины — это отличный функционал, доступный в языке Kotlin. Я уже опробовал его и мне он очень понравился.
Цель этой статьи — помочь вам понять Корутины. Просто будьте внимательны при прочтении и у вас всё получится.
Начнем с официального определения Корутин.
Корутины — это новый способ написания асинхронного, неблокирующего кода.
Первый вопрос, возникающий при прочтении этого определения — чем Корутины отличаются от потоков?
Корутины — это облегчённые потоки. Облегчённый поток означает, что он не привязан к нативному потоку, поэтому он не требует переключения контекста на процессор, поэтому он быстрее.
Что это значит, «не привязан к нативному потоку»?
Корутины есть во многих языках программирования.
В принципе, есть два типа Корутин:
Kotlin реализует Корутины без стека — это значит, что в Корутинах нет собственного стека, поэтому они не привязываются к нативному потоку.
И Корутины, и потоки являются многозадачными. Но разница в том, что потоки управляются ОС, а Корутины пользователями.
Теперь вы можете осознанно прочитать и понять выдержку с официального сайта Kotlin:
Корутины можно представить в виде облегчённого потока. Подобно потокам, корутины могут работать параллельно, ждать друг друга и общаться. Самое большое различие заключается в том, что корутины очень дешевые, почти бесплатные: мы можем создавать их тысячами и платить очень мало с точки зрения производительности. Потоки же обходятся дорого. Тысяча потоков может стать серьезной проблемой даже для современной машины.
Давайте посмотрим, как работать с Корутинами
Итак, как запустить корутину (по аналогии с запуском потока)?
Есть две функции для запуска корутины:
launch<> vs async<> в Корутинах Kotlin
Давайте посмотрим на использование launch<>
Этот код запустит новую корутину в данном пуле потоков. В этом случае мы используем CommonPool, который использует ForkJoinPool.commonPool(). Потоки все ещё существуют в программе, основанной на корутинах, но один поток может запускать много корутин, поэтому нет необходимости в слишком большом количестве потоков.
Попробуем одну вещь:
Если вы cделаете это прямо в основной функции, то получите сообщение об ошибке:
Функции прерывания можно вызвать только из корутины или другой функции прерывания.
Функция задержки является функцией прерывания, соответственно мы можем ее вызывать только из корутины или другой функции прерывания.
Давайте исправим это:
Теперь посмотрим на использование async<>
runBlocking in Kotlin Coroutines with Example
As it is known that when the user calls the delay() function in any coroutine, it will not block the thread in which it is running, while the delay() function is called one can do some other operations like updating UI and many more things. As the delay function is a suspend function it has to be called from the coroutine or another suspend function.
Definition of runBlocking() function
According to official documentation, the runBlocking() function may be defined as:
runBlocking is a coroutine function. By not providing any context, it will get run on the main thread.Runs a new coroutine and blocks the current thread interruptible until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
Kotlin
Log Output:
Log-Output of the above program (Timestamps in seconds is shown by Oval Circle in image)
As it can be seen in the log-output that the “User is in the Global Scope” gets printed after the Log of “User is not in the Global Scope” which shows that the GlobalScope start a coroutine, which does not block the main thread, and the other operations can be performed while the delay time get’s over. But when someone wants to call only the suspend function and do not need the coroutine behavior, one can call the suspend function from the runBlocking. So when one wants to call any suspend functions such as delay() and do not care about the asynchronous nature, one can use the runBlocking function. The difference between the calling the suspend function from the GlobalScope.launch and calling the suspend function (eg delay()) from runBlocking is that runBlocking will block the main thread or the thread in which it is used and GlobalScope.launch will not block the main thread, in this case, UI operations can be performed while the thread is delayed.
Another use-case of runBlocking is for testing of the JUnit, in which one needs to access the suspend function from within the test function. One case also uses the runBlocking in order to learn the coroutines in-depth in order to figure out how they work behind the scenes. Let’s see from the examples below how runBlocking actually works:
Основы сопрограмм
В этом разделе рассматриваются основные концепции сопрограмм.
Ваша первая сопрограмма
Запустите следующий код:
Результат выполнения кода будет следующим:
Это связано с тем, что delay является функцией приостановки, которая не блокирует поток, а приостанавливает сопрограмму. Использовать её можно только из сопрограммы.
Связываем блокирующий и неблокирующий миры
Таким же образом можно писать модульные тесты для функций приостановки:
Структурированный параллелизм
Есть более лучшее решение. В нашем коде можно использовать структурированный параллелизм. Вместо запуска сопрограмм в GlobalScope, как мы обычно делаем с потоками (потоки всегда глобальные), мы можем запускать сопрограммы в области видимости выполняемой нами операции.
Scope builder
В дополнение к CoroutineScope, предоставляемой разными билдерами, можно объявить свою собственную область видимости с помощью билдера coroutineScope. Он создает область видимости и не завершается, пока не завершатся все запущенные дочерние сопрограммы.
Следующий пример это демонстрирует:
Обратите внимание, что сразу после сообщения «Task from coroutine scope» (во время ожидания выполнения вложенного launch) выполняется и выдаётся «Task from runBlocking», хотя выполнение coroutineScope еще не завершилось.
Извлечение функции
Легковесные сопрограммы
Запустите следующий код:
Данный код запускает 100 тысяч сопрограмм, каждая из которых через 5 секунд печатает точку.
А теперь попробуйте сделать то же самое с потоками. Что произойдёт? (Скорее всего это вызовет ошибку, связанную с нехваткой памяти).
Глобальные сопрограммы похожи на демон-потоки
Нижеприведённый код запускает длительную сопрограмму в GlobalScope, которая два раза в секунду выводит сообщение «I’m sleeping», а затем, после некоторой задержки, происходит возврат из функции main :
Если вы запустите данный код, то увидите, что он трижды выводит сообщение и завершается:
Активные сопрограммы, запущенные в GlobalScope, не поддерживают «жизнь» процесса. В этом они похожи на демон-потоки.






