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

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

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

Начать курс

Введение в асинхронное программирование в Kotlin

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

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

1. Основы корутин: что это и как работает?

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

Подключение зависимости

Добавьте в файл build.gradle.kts:

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

Первая корутина

Для запуска корутины используется билдер launch. Он запускает новую корутину в фоновом режиме.

import kotlinx.coroutines.*

fun main() = runBlocking { // runBlocking блокирует главный поток для ожидания корутин
    println("Начало: ${Thread.currentThread().name}")

    launch { // запуск новой корутины
        delay(1000L) // приостанавливаем на 1 секунду (не блокирует поток!)
        println("Корутина: ${Thread.currentThread().name}")
    }

    println("Конец: ${Thread.currentThread().name}")
}

Вывод:

Начало: main
Конец: main
Корутина: main

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

2. Suspend-функции: строительные блоки асинхронности

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

Пример suspend-функции

import kotlinx.coroutines.*

suspend fun fetchData(): String {
    delay(2000L) // имитация долгого сетевого запроса
    return "Данные получены"
}

fun main() = runBlocking {
    println("Запрос данных...")
    val result = fetchData() // вызов suspend-функции из корутины
    println(result)
}

Параллельное выполнение с async/await

Если нужно выполнить несколько задач параллельно, используйте билдер async. Он возвращает Deferred — объект, который хранит будущий результат. Получить результат можно с помощью await().

import kotlinx.coroutines.*

suspend fun loadUser(): String {
    delay(1000L)
    return "Пользователь: Иван"
}

suspend fun loadPosts(): String {
    delay(1500L)
    return "Посты: Новости, Статьи"
}

fun main() = runBlocking {
    val time = measureTimeMillis {
        val userDeferred = async { loadUser() }
        val postsDeferred = async { loadPosts() }

        println(userDeferred.await())
        println(postsDeferred.await())
    }
    println("Общее время: $time мс") // ~1500 мс (максимальное время из двух)
}

Без async задачи выполнялись бы последовательно и заняли бы ~2500 мс. Параллельное выполнение сократило время до ~1500 мс.

3. Dispatchers: управление потоками

Dispatcher определяет, на каком потоке или пуле потоков будет выполняться корутина. В Kotlin есть несколько встроенных диспетчеров:

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

Пример использования Dispatchers

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch(Dispatchers.IO) {
        println("IO диспетчер: ${Thread.currentThread().name}")
        // эмуляция сетевого запроса
        delay(1000L)
    }

    launch(Dispatchers.Default) {
        println("Default диспетчер: ${Thread.currentThread().name}")
        // эмуляция сложных вычислений
        var sum = 0L
        for (i in 1..100_000) sum += i
    }
}

Смена диспетчера с помощью withContext

Функция withContext позволяет временно переключить контекст выполнения внутри корутины. Это часто используется для выполнения IO-операций, а затем возврата в UI-поток.

import kotlinx.coroutines.*

suspend fun loadData(): String = withContext(Dispatchers.IO) {
    println("Загрузка в: ${Thread.currentThread().name}")
    delay(2000L)
    "Результат"
}

fun main() = runBlocking {
    println("Главный поток: ${Thread.currentThread().name}")
    val data = loadData() // выполняется в IO, но результат возвращается в main
    println("Данные: $data")
}

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

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

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

import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Поймано исключение: $                

Блоги

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