Spec-Zone.ru › Kotlin 1.7

Основы сопроцедур

В этом разделе рассматриваются основные понятия сопроцедур.

Ваша первая сопроцедура

A coroutine — это экземпляр приостанавливаемого вычисления. По концепции она похожа на поток, в том смысле, что она принимает блок кода для выполнения в параллели с остальным кодом. Однако сопроцедура не привязана к какому-либо конкретному потоку. Она может приостановить своё выполнение в одном потоке и возобновить в другом.

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

Запустите следующий код, чтобы получить свою первую работающую сопроцедуру:

import kotlinx.coroutines.*

//sampleStart
fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
}
//sampleEnd

Полный код можно найти здесь.

Вы увидите следующий результат:

Hello
World!

Давайте разберём, что делает этот код.

launch — это строитель сопроцедур. Она запускает новую сопроцедуру параллельно с остальным кодом, который продолжает работать независимо. Именно поэтому Hello было выведено первым.

delay — это специальная приостанавливаемая функция. Она приостанавливает выполнение сопроцедуры на определённое время. Приостановка сопроцедуры не блокирует основной поток, а позволяет другим сопроцедурам работать и использовать основной поток для своего кода.

runBlocking также является строителем сопроцедур, который соединяет мир без сопроцедур обычного fun main() и код с сопроцедурами внутри runBlocking { ... } фигурных скобок. Это выделяется в IDE подсказкой this: CoroutineScope сразу после runBlocking открывающей фигурной скобки.

Если вы удалите или забудете runBlocking в этом коде, вы получите ошибку в вызове launch, так как launch объявлена только в CoroutineScope:

Unresolved reference: launch

Название runBlocking означает, что поток, который её выполняет (в данном случае — основной поток), блокируется на время вызова, до тех пор, пока все сопроцедуры внутри runBlocking { ... } не завершат своё выполнение. Часто runBlocking используется на самом верхнем уровне приложения и редко внутри реального кода, так как потоки являются дорогостоящими ресурсами, а их блокировка является неэффективной и часто нежелательной.

Структурированная параллельность

Сопроцедуры следуют принципу структурированной параллельности, что означает, что новые сопроцедуры могут быть запущены только в определённом CoroutineScope, который ограничивает срок действия сопроцедуры. Приведенный выше пример демонстрирует, что runBlocking устанавливает соответствующий контекст, и поэтому предыдущий пример ожидает, пока World! не будет напечатано после задержки в секунду и только затем завершает работу.

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

Рефакторинг: Вынесение функции

Давайте вынесем блок кода внутри launch { ... } в отдельную функцию. При рефакторинге "Вынести функцию" для этого кода вы получите новую функцию с модификатором suspend. Это ваша первая приостанавливаемая функция. Приостанавливаемые функции могут использоваться внутри сопроцедур, как и обычные функции, но их дополнительная особенность заключается в том, что они, в свою очередь, могут использовать другие приостанавливаемые функции (например, delay в данном примере) для приостановки выполнения сопроцедуры.

import kotlinx.coroutines.*

//sampleStart
fun main() = runBlocking { // this: CoroutineScope
    launch { doWorld() }
    println("Hello")
}

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)
    println("World!")
}
//sampleEnd

Полный код можно найти здесь.

Строитель контекста

В дополнение к контексту сопроцедуры, предоставляемому различными строителями, вы можете объявить свой собственный контекст с помощью coroutineScope. Она создаёт контекст сопроцедуры и не завершается, пока не завершат работу все запущенные дочерние сопроцедуры.

runBlocking и coroutineScope строители могут выглядеть похожими, потому что оба ждут завершения своего тела и всех дочерних сопроцедур. Основное отличие заключается в том, что метод runBlocking блокирует текущий поток для ожидания, в то время как coroutineScope просто приостанавливает выполнение, освобождая основной поток для других задач. Из-за этого отличия runBlocking — обычная функция, а coroutineScope — приостанавливаемая функция.

Вы можете использовать coroutineScope из любой приостанавливаемой функции. Например, вы можете перенести одновременную печать Hello и World в функцию suspend fun doWorld().

import kotlinx.coroutines.*

//sampleStart
fun main() = runBlocking {
    doWorld()
}

suspend fun doWorld() = coroutineScope {  // this: CoroutineScope
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello")
}
//sampleEnd

Полный код можно найти здесь.

Этот код также выведет:

Hello
World!

Строитель контекста и параллельность

Строитель coroutineScope можно использовать внутри любой приостанавливаемой функции для выполнения нескольких параллельных операций. Давайте запустим две параллельные сопроцедуры внутри приостанавливаемой функции doWorld.

import kotlinx.coroutines.*

//sampleStart
// Sequentially executes doWorld followed by "Done"
fun main() = runBlocking {
    doWorld()
    println("Done")
}

// Concurrently executes both sections
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
    launch {
        delay(2000L)
        println("World 2")
    }
    launch {
        delay(1000L)
        println("World 1")
    }
    println("Hello")
}
//sampleEnd

Полный код можно найти здесь.

Оба блока кода внутри launch { ... } выполняются параллельно, с World 1 выведенным первым, после секунды с начала, и World 2 выведенным следующим, после двух секунд с начала. coroutineScope в doWorld завершается только после завершения обоих, поэтому doWorld возвращается и позволяет вывести строку Done только после этого:

Hello
World 1
World 2
Done

Явное задание

Строитель сопроцедур launch возвращает объект Job, который является обработчиком запущенной сопроцедуры и может использоваться для явного ожидания её завершения. Например, можно дождаться завершения дочерней сопроцедуры, а затем напечатать строку "Готово":

import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
    val job = launch { // launch a new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello")
    job.join() // wait until child coroutine completes
    println("Done") 
//sampleEnd    
}

Полный код можно найти здесь.

Этот код выдаёт:

Hello
World!
Done

Корутины легковесные

Корутины менее ресурсоёмкие, чем потоки JVM. Код, который исчерпывает доступную память JVM при использовании потоков, можно выразить с помощью корутин, не достигая пределов ресурсов. Например, следующий код запускает 100000 отдельных корутин, которые ожидают 5 секунд, а затем выводят точку ('.') при потреблении очень малого объёма памяти:

import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(100_000) { // launch a lot of coroutines
        launch {
            delay(5000L)
            print(".")
        }
    }
}

Полный код можно найти здесь.

Если написать ту же программу, используя потоки (удалить runBlocking, заменить launch на thread, и заменить delay на Thread.sleep), она, скорее всего, потребляет слишком много памяти и вызовет ошибку из-за недостатка памяти.

Последнее изменение: 27 июня 2022 г.
Руководство по корутинам Корутины и каналы − учебное пособие

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/coroutines-basics.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API