«Перепроектирование вашего приложения для работы в многопоточном режиме на многоядерной машине немного похоже на обучение плаванию, прыгнув в глубокую часть». - Херб Саттер, председатель комитета стандартов ISO C ++, Microsoft®

  • Что такое сопрограммы?
  • Блокирование и неблокирование
  • Котлинские сопрограммы
  • Приостановка функций
  • CoroutineScope
  • Строители сопрограмм
  • Диспетчер сопрограмм
  • Запуск сопрограммы
  • Заключение

Сопрограммы существуют уже довольно давно. Фактически, это одна из идей, которые помогли разработать многозадачные операционные системы. В большинстве случаев сопрограмма представляет собой обобщение подпрограммы или функции, которая в среде без вытеснения (вкратце, операционная система или ОС) может добровольно отдавать процессорное время, чтобы другие такие подпрограммы могли использовать его для себя, не теряя результатов предыдущих вычислений. а затем можно продолжить, откуда бы он ни уехал. Следовательно, сопрограммы в основном являются подпрограммами, которые жертвуют своим процессорным временем, чтобы система в целом могла победить - Go, сопрограммы Go.

Многозадачные операционные системы - это тип операционных систем, в которых несколько задач могут выполняться одновременно (не параллельно) с помощью переключения контекста.

Среды ОС без вытеснения - это многозадачные среды ОС, в которых процесс передает управление (время ЦП) добровольно, чтобы другие приложения могли работать одновременно.

Блокировка Vs. Неблокирующие звонки

Блокирующий вызов в процессе или программе - это инструкция или список инструкций, которые намеренно или непреднамеренно блокируют дальнейшую обработку потока до тех пор, пока этот вызов не завершится. С другой стороны, если бы этот же вызов был в противном случае отправлен в другой поток для выполнения, чтобы основной поток продолжал выполняться асинхронно, тогда вызов был бы неблокирующим. В неблокирующих вызовах родительский поток уведомляется с результатами с помощью обратного вызова.

//A blocking call
fun main(args: Array<String>){
println("Hello {Blocking}")
Thread.sleep(2000)
println("World")
}
//A non-blocking call
fun main(args: Array<String>){
println("Hello {NonBLocking}")
val thread = Thread(Runnable{
Thread.sleep(2000)
println("World")
})
thread.start()
for (var i in 0..10){
println("Process $i")
}
thread.join()
}

Kotlin Coroutine

Сопрограммы в kotlin реализованы как недолговечные легковесные потоки (а не потоки, которые представляет объект Thread). Они отправляются с помощью ThreadDispatcher или WorkerDispatcher, и их жизнеспособность зависит от области, в которой они были созданы и затем запущены.

Многие языки имеют поддержку сопрограмм на уровне языка, но то, что отличает подход kotlin от них, заключается в том, что koltin (по моему мнению) предоставляет хороший интерфейс для управления и контроля над ними. Кроме того, поскольку kotlin по своей сути функциональный, он более подробен и проще писать в нем сопрограммы. Давайте посмотрим на простой пример

//maven: https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
import kotlinx.coroutines.*
fun main(args: Array<String>){
println("Hello ")
GlobalScope.launch{
delay(2000)
println("World")
}
//waiting for corutine to finish
Thread.sleep(3000)
}

Во второй строке мы импортируем в программу определение сопрограммы и соответствующих методов. Затем мы создаем и запускаем сопрограмму в строке 5 с запуском построителя сопрограмм в GlobalScope. Внутри сопрограммы мы вызвали задержку неблокирующей функции приостановки с количеством времени в миллисекундах, на которое мы хотим задержать сопрограмму, и, наконец, мы печатаем «World». Обратите внимание, что вызов этой сопрограммы не блокирует основной поток. Наконец, мы сделали блокирующий вызов метода сна в основном потоке, чтобы пережить сопрограмму и получить результаты обратно.

Есть много слов, не имеющих смысла, не так ли? Давайте пройдемся по ним.

Что такое функция приостановки?

Приостановочная функция, как указано в ее названии, - это функция, выполнение которой, как мы ожидаем, займет время. И, следовательно, потенциально может заблокировать поток, чего мы не хотим делать с основным потоком. В Kotlin есть ключевое слово suspend, которое указывает компилятору, что следующая за ним функция (приостановка) является функцией приостановки и должна выполняться внутри сопрограммы. Например задержка.

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

import kotlinx.coroutines.*
suspend fun thisWillTakeTime(time: Long): String{
delay(time)
return "You had me waiting for ${time/1000} seconds."
}
fun main(args: Array<String>){
//thisWillTakeTime(2000) => Error: Cannot call suspending function in non-suspend env
val job = GlobalScope.launch{
//waiting for 3 seconds
println(thisWillTakeTime(3000))
}
while(!job.isCompleted)
{ /* or Thread.sleep(3000) */}
}

В строке 2 мы указали, что этот метод приостанавливается, пометив его как приостановить. Функция приостановки может вызывать как приостанавливающие, так и не приостанавливающие функции внутри своего блока, поэтому вызов задержки не беспокоит компилятор. В строке 8 (комментарий), если вы раскомментируете эту строку, вы получите сообщение об ошибке от компилятора, т.е. «Функция приостановки не может быть вызвана из контекста без приостановки». В строках с 9 по 12 мы запустили новая сопрограмма с запуском конструктора сопрограмм, который возвращает экземпляр класса задания, с помощью которого вы можете запрашивать различные сведения о сопрограмме, а именно. isCompleted, isCancelled и isActive. Наконец, мы вошли в бесконечный цикл while, который продолжает объединять задание, запрашивая его завершение, пока оно не будет выполнено. Если бы мы этого не сделали, то основной метод умер бы еще до того, как завершилась бы сопрограмма.

Что такое Coroutine Scope?

Область действия сопрограммы, такая как GlobalScope, тривиально - это время жизни, в течение которого сопрограмма живет или работает. Каждая область видимости состоит из coroutineContext, который отслеживает выполнение сопрограммы. Каждый конструктор сопрограмм является расширением CoroutineScope и, следовательно, наследует контекст, который поставляется с областью видимости, чтобы он мог распространять как элементы контекста, так и отмену на сопрограммы, построенные с его помощью.

Конструктор сопрограмм - это функциональное расширение над coroutineScope, используемое для создания и запуска сопрограмм.

//Signature of launch coroutine builder
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
onCompletion: CompletionHandler? = null,
block: suspend CoroutineScope.() -> Unit
): Job {...}
//Signature of async coroutine builder
fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> (source)

Обычный конструктор сопрограмм

  • запуск

"запустить" - запустить и забыть сопрограмму означает, что вас не волнуют результаты; все, что вы хотите добиться, - это чтобы инструкции выполнялись при запуске, не блокируя основной поток, например, сохраняя изображение или файл. Однако, когда исключение происходит во время выполнения запуска, оно распространяется на родительский объект, что может привести к сбою приложения.

  • асинхронный

async выполняет задачу асинхронно, а затем обрабатывает результат, когда он доступен. Он возвращает экземпляр отложенного объекта, который является Future с результатами. Например сетевые звонки.

  • runBlocking

runBlocking предназначен для тестирования обычного кода сопрограмм, поэтому его не следует использовать, когда он не требуется. Он потенциально блокирует поток и ожидает завершения всех других сопрограмм, то есть присоединения.

Что такое диспетчер сопрограмм?

Диспетчер сопрограмм - это экземпляр одноэлементного абстрактного класса CoroutineDispatcher, который наследует ContinuationInterceptor. ContinuationInterceptor - это класс, который отслеживает возобновление сопрограммы после цикла приостановки. Он перехватывает продолжение и отправляет сопрограмму в пул потоков. Ниже приведен список диспетчеров, предоставляемый kotlin-coroutine api.

  • Диспетчеры. По умолчанию

CoroutineDispatcher по умолчанию, который используется всеми стандартными сборщиками, такими как запуск, асинхронность и т. Д. Он поддерживается общим пулом потоков на JVM. Уровень приоритета этих потоков равен количеству ядер ЦП.

  • Диспетчеры. IO

Этот диспетчер специально разработан для отправки блокирующих задач ввода-вывода в общий пул потоков. Дополнительные потоки в этом пуле создаются и отключаются по запросу. Количество потоков, используемых этим диспетчером, ограничено значением «kotlinx.coroutines.io.parallelism».

  • Диспетчеры. Неограниченный

Сопрограммы, отправляемые с помощью неограниченного диспетчера, не ограничиваются каким-либо потоком или пулом потоков, и, следовательно, они выполняются в одном кадре вызова (потоке) до первой приостановки. Обратите внимание, что это все еще эксперимент.

CoroutineStart

Последний, но не менее важный фрагмент головоломки - CoroutineStart, CoroutineStart определяет параметр запуска для сопрограммы в CoroutineBuilder (см. Подпись запуска).

  • CoroutineStart. ПО УМОЛЧАНИЮ

Он немедленно планирует выполнение сопрограммы в соответствии с ее контекстом. Обычно исполнение с запуском и асинхронным запуском.

  • CoroutineStart. LAZY

Coroutine запускается лениво, только когда это необходимо. Это может пригодиться в тех случаях, когда вы сначала хотите дождаться чего-то или какого-то события, прежде чем запускать сопрограмму.

  • CoroutineStart. ATOMIC

Атомарно (без возможности отмены) планирует выполнение сопрограммы в соответствии с ее контекстом. То есть он должен выполняться, если не возникает какое-либо внутреннее исключение.

  • CoroutineStart. НЕ ИСПОЛЬЗУЕТСЯ

Он немедленно выполняет сопрограмму до своей первой точки приостановки в текущем потоке. Однако, когда сопрограмма возобновляется из приостановки, она отправляется в соответствии с CoroutineDispatcher в своем контексте.

Заключение

Ну вот и все народ. Я надеюсь, что эта статья помогла вам разобраться в сопрограммах и их реализации в Kotlin. Если у вас есть какие-либо вопросы, вы можете задать их в разделе комментариев. Я знаю, что эта статья носит скорее теоретический характер, и не беспокойтесь, в следующей мы реализуем несколько реальных примеров с сопрограммами в разработке для Android. Прокомментируйте, если вам понравилось; Следуйте, если вам это понравилось (покажите немного любви на). Приятно провести время.

Первоначально опубликовано на streamofbytes.blogspot.com.