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

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

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

Начать курс

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

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

1. Основы корутин в Kotlin

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

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

Для начала добавьте зависимости в build.gradle.kts:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") // для Android
}

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

Создадим простую корутину с помощью runBlocking — моста между синхронным и асинхронным миром:

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L) // неблокирующая задержка
        println("Мир!")
    }
    println("Привет,")
    delay(2000L) // ждём завершения корутины
}

В этом примере launch запускает новую корутину, а delay — это приостанавливающая функция (suspend), которая не блокирует поток.

2. Suspend функции и структурированный параллелизм

2.1. Создание suspend функций

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

suspend fun fetchUserData(userId: String): User {
    delay(2000L) // имитация сетевого запроса
    return User(userId, "Иван Петров")
}

suspend fun fetchUserPosts(userId: String): List<Post> {
    delay(1500L)
    return listOf(Post("Заголовок 1"), Post("Заголовок 2"))
}

2.2. Последовательное выполнение

По умолчанию корутины выполняются последовательно внутри одного launch:

fun main() = runBlocking {
    val time = measureTimeMillis {
        val user = fetchUserData("123")
        val posts = fetchUserPosts("123")
        println("${user.name} имеет ${posts.size} постов")
    }
    println("Время выполнения: $time мс") // ~3500 мс
}

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

Для параллельного выполнения используйте async и await:

fun main() = runBlocking {
    val time = measureTimeMillis {
        val user = async { fetchUserData("123") }
        val posts = async { fetchUserPosts("123") }
        
        println("${user.await().name} имеет ${posts.await().size} постов")
    }
    println("Время выполнения: $time мс") // ~2000 мс (параллельно)
}

3. Диспетчеры корутин и контексты

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

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

3.1. Пример с Dispatchers.IO

fun main() = runBlocking {
    launch(Dispatchers.IO) {
        // Сетевой запрос
        val response = httpClient.get("https://api.example.com/data")
        withContext(Dispatchers.Main) {
            // Обновление UI
            textView.text = response.body()
        }
    }
}

3.2. withContext для переключения контекста

Функция withContext позволяет временно сменить диспетчер внутри корутины:

suspend fun loadData(): String = withContext(Dispatchers.IO) {
    // Тяжелая операция в фоне
    Thread.sleep(1000)
    "Данные загружены"
}

4. Flow — реактивные потоки данных

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

4.1. Создание простого Flow

import kotlinx.coroutines.flow.*

fun simpleFlow(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(1000L)
        emit(i) // эмитируем значение
    }
}

fun main() = runBlocking {
    simpleFlow().collect { value ->
        println("Получено: $value")
    }
}

4.2. Трансформация Flow

Flow поддерживает все операторы, знакомые по RxJava/Kotlin Sequences:

fun main() = runBlocking {
    simpleFlow()
        .map { it * 2 }
        .filter { it % 3 == 0 }
        .catch { e -> println("Ошибка: $e") }
        .collect { println("Результат: $it") }
}

4.3. StateFlow и SharedFlow

Для управления состоянием используйте StateFlow (хранит последнее значение) и SharedFlow (для широковещательных событий):

class ViewModel {
    private val _state = MutableStateFlow("Начальное состояние")
    val state: StateFlow<String> = _state.asStateFlow()
    
    fun update() {
        viewModelScope.launch {
            _state.value = "Новое состояние"
        }
    }
}

5. Практические паттерны и лучшие практики

5.1. Обработка ошибок

Блоги

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