← Назад к вопросам

Какие знаешь способы запуска coroutine?

2.3 Middle🔥 242 комментариев
#Kotlin основы#Многопоточность и асинхронность

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные способы запуска корутин в Kotlin

В Kotlin Coroutines существует несколько строительных функций (builder functions) для запуска корутин. Выбор конкретного способа зависит от требуемого поведения, обработки исключений и необходимости получения результата.

1. launch - Запуск "фоновой" корутины

Это наиболее распространённый способ для запуска задачи, результат которой не требуется немедленно или не требуется вообще (fire-and-forget). Функция возвращает объект Job, через который можно управлять жизненным циклом корутины (отмена, ожидание завершения).

Ключевые особенности:

  • Не возвращает результат (возвращает Job).
  • Необработанные исключения внутри launch приводят к краху родительской корутины (и приложения, если не установлен CoroutineExceptionHandler), если только она не запущена в SupervisorJob.
  • Используется для side-effect операций (логирование, обновление UI, отправка аналитики).
import kotlinx.coroutines.*

fun main() = runBlocking {
    // Запуск в текущем CoroutineScope (runBlocking)
    val job: Job = launch {
        delay(1000L)
        println("Корутина, запущенная через launch, завершила работу")
    }
    println("Основной поток продолжает работу немедленно")
    job.join() // Ожидание завершения дочерней корутины
}

2. async - Запуск корутины с возвратом результата (Deferred)

Используется, когда необходимо выполнить вычисление асинхронно и получить его результат в будущем. Возвращает объект Deferred<T> (наследник Job), который содержит будущий результат типа T.

Ключевые особенности:

  • Возвращает Deferred<T>, из которого результат извлекается с помощью await().
  • Исключения внутри async НЕ выбрасываются немедленно, а откладываются до вызова await().
  • Позволяет легко выполнять конкурентные (параллельные) вычисления.
import kotlinx.coroutines.*

fun main() = runBlocking {
    // Параллельный запуск двух асинхронных задач
    val deferredNumber: Deferred<Int> = async {
        delay(500L)
        42 // Последнее выражение - результат
    }
    val deferredString: Deferred<String> = async {
        delay(300L)
        "Hello"
    }

    // await() приостанавливает корутину, пока результат не будет готов
    val number = deferredNumber.await()
    val string = deferredString.await()
    println("Результат: $string $number") // Результат: Hello 42

    // Если не нужны промежуточные результаты, можно использовать awaitAll
    val sum = awaitAll(async { 1 }, async { 2 }).sum()
    println(sum) // 3
}

3. runBlocking - Блокирующий запуск

Эта функция создаёт новую корутину и блокирует текущий поток до её полного завершения. Используется главным образом в точках входа в программу (main), в тестах или для интеграции блокирующего кода в асинхронный мир.

Важное предупреждение: Её не следует использовать внутри обычных suspend-функций или корутин, так как это противоречит идее неблокирующей асинхронности.

import kotlinx.coroutines.*

fun main() { // Обычная функция main
    // runBlocking блокирует основной поток до завершения своей лямбды
    val result = runBlocking {
        delay(1000L)
        "Результат выполнен в блокирующей корутине"
    }
    println(result)
    // До этой строки выполнение дойдёт только через 1 секунду.
}

4. coroutineScope и supervisorScope - Структурированный параллелизм

Эти suspend-функции используются для создания собственных областей видимости (scope) внутри suspend-функций. Они приостанавливают вызывающую корутину до завершения всех своих дочерних корутин.

  • coroutineScope: При ошибке в любом дочернем launch отменяет все остальные дочерние корутины и выбрасывает исключение.
  • supervisorScope: При ошибке в дочернем launch не отменяет другие дочерние корутины. Исключение нужно обрабатывать внутри дочерней корутины.
import kotlinx.coroutines.*

suspend fun performParallelTasks() = coroutineScope {
    // Все launch внутри этой области - дочерние корутины
    launch { task1() }
    launch { task2() }
    // Функция performParallelTasks завершится только после task1 И task2
}

suspend fun task1() { delay(1000L) }
suspend fun task2() { delay(1500L) }

5. produce - Запуск корутины для асинхронных потоков данных

Специальный билдер, который создаёт корутину, возвращающую ReceiveChannel. Предназначен для реализации паттерна Producer-Consumer (поставщик-потребитель). В современных версиях библиотеки чаще рекомендуется использовать Flow.

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

fun main() = runBlocking {
    val channel: ReceiveChannel<Int> = produce {
        for (x in 1..5) {
            send(x * x) // Отправляем данные в канал
            delay(100L)
        }
    }

    // Потребляем данные из канала
    for (value in channel) {
        println(value) // 1, 4, 9, 16, 25
    }
}

Итог и критерии выбора

  • launch — для фоновой задачи без результата.
  • async — для асинхронного вычисления с результатом, особенно при параллельных операциях.
  • runBlocking — только для перехода из синхронного мира в асинхронный (main, тесты).
  • coroutineScope/supervisorScope — для структурирования кода внутри suspend-функций, требующих параллельного выполнения.
  • produce — для создания асинхронных потоков данных через каналы (чаще заменяется на Flow).

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