Асинхронное программирование в Kotlin: полное руководство для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Асинхронное программирование в Kotlin: полное руководство для начинающих

Современные приложения всё чаще требуют выполнения нескольких задач одновременно: загрузка данных из сети, работа с базой данных, обработка пользовательского ввода. В языке Kotlin для этого используется асинхронное программирование, основанное на корутинах (coroutines). Это мощный и элегантный инструмент, который позволяет писать неблокирующий код без сложных коллбэков.

В этой статье мы разберём основы асинхронного программирования в Kotlin: что такое корутины, как использовать launch, async и await, а также познакомимся с диспетчерами и обработкой ошибок. Материал рассчитан на начинающих разработчиков, уже знакомых с основами синтаксиса Kotlin.

Что такое корутины и зачем они нужны?

Корутина — это легковесный поток выполнения, который может быть приостановлен и возобновлён без блокировки основного потока. В отличие от традиционных потоков (threads), корутины не привязаны к системным ресурсам и могут выполняться в одном потоке, переключаясь между собой.

Основные преимущества корутин:

  • Лёгкость — можно запустить тысячи корутин без значительного потребления памяти.
  • Приостановка (suspend) — функция может "заморозить" своё выполнение, не блокируя поток.
  • Структурированная конкурентность — корутины автоматически управляют своим жизненным циклом.
  • Простота — код выглядит как синхронный, без вложенных коллбэков.

Для работы с корутинами необходимо добавить зависимость в build.gradle.kts:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
}

Основные строительные блоки: launch, async и suspend

Функция suspend

Любая функция, которая может приостанавливать выполнение корутины, помечается ключевым словом suspend. Такие функции можно вызывать только внутри корутины или другой suspend-функции.

suspend fun fetchUserData(): String {
    delay(1000) // имитация задержки
    return "User data loaded"
}

Функция delay() — это аналог Thread.sleep(), но без блокировки потока.

launch — запуск корутины без результата

Билдер launch запускает новую корутину, которая не возвращает результат (возвращает Job). Используется для задач типа "запустил и забыл".

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        println("Корутина 1 запущена")
        delay(1000)
        println("Корутина 1 завершена")
    }
    println("Главный поток продолжает работу")
}

Вывод:

Главный поток продолжает работу
Корутина 1 запущена
Корутина 1 завершена

Обратите внимание: runBlocking используется только в учебных примерах или в точках входа (например, в main()). В реальных приложениях (Android, сервер) его применять не нужно.

async и await — получение результата

Если корутина должна вернуть результат, используется билдер async. Он возвращает объект Deferred<T>, из которого можно получить значение с помощью await().

fun main() = runBlocking {
    val deferredResult: Deferred<String> = async {
        delay(1500)
        "Результат вычислений"
    }

    println("Ожидаем результат...")
    val result = deferredResult.await()
    println("Получен результат: $result")
}

Вывод:

Ожидаем результат...
Получен результат: Результат вычислений

С помощью async можно запускать несколько параллельных задач:

fun main() = runBlocking {
    val task1 = async { loadDataFromNetwork() }
    val task2 = async { loadDataFromDatabase() }

    val result1 = task1.await()
    val result2 = task2.await()

    println("Оба результата получены: $result1, $result2")
}

suspend fun loadDataFromNetwork(): String {
    delay(2000)
    return "Network data"
}

suspend fun loadDataFromDatabase(): String {
    delay(1500)
    return "Database data"
}

Общее время выполнения составит ~2 секунды (максимальная задержка), а не 3.5 секунды, если бы задачи выполнялись последовательно.

Диспетчеры (Dispatchers) и контекст корутины

Диспетчер определяет, в каком потоке или пуле потоков будет выполняться корутина. Основные диспетчеры:

  • Dispatchers.Default — для CPU-интенсивных задач (по умолчанию используется, если не указан другой).
  • Dispatchers.IO — для операций ввода-вывода (работа с файлами, сетью, БД).
  • Dispatchers.Main — для UI-потока (доступен в Android или JavaFX).
  • Dispatchers.Unconfined — не привязан к конкретному потоку (используется редко).

Пример использования диспетчера:

fun main() = runBlocking {
    launch(Dispatchers.IO) {
        println("Выполняется в IO-потоке: ${Thread.currentThread().name}")
        // работа с сетью или файлами
    }
    launch(Dispatchers.Default) {
        println("Выполняется в Default-потоке: ${Thread.currentThread().name}")
        // сложные вычисления
    }
}

Также можно создавать собственные диспетчеры с помощью newSingleThreadContext() или newFixedThreadPoolContext().

Обработка ошибок и отмена корутин

Обработка исключений

Исключения внутри корутины можно обрабатывать с помощью try-catch или через CoroutineExceptionHandler.

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Ошибка: ${exception.message}")
    }

    val job = launch(                

Блоги

Рекомендаций по книгам