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

Как работает async вместе с await в корутинах?

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

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

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

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

Механизм async и await в Kotlin Coroutines

async и await — это ключевые строительные блоки для параллельного выполнения и асинхронных вычислений в корутинах Kotlin. Они работают в связке, позволяя запускать задачи конкурентно и легко получать их результаты.

Основная концепция

async — это функция-билдер корутин, которая запускает новую корутину и возвращает объект Deferred<T> (отложенное значение). Deferred — это аналог Future или Promise в других языках, представляющий вычисление, результат которого будет доступен в будущем.

await() — это функция-приостановка (suspend), которая вызывается на объекте Deferred и ожидает завершения соответствующей корутины, возвращая её результат.

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

import kotlinx.coroutines.*

suspend fun main() {
    val time = measureTimeMillis {
        val deferredResult1: Deferred<Int> = async { fetchData1() }
        val deferredResult2: Deferred<Int> = async { fetchData2() }
        
        // await() приостанавливает корутину, но не блокирует поток
        val result1 = deferredResult1.await() // Ждём результат первой задачи
        val result2 = deferredResult2.await() // Ждём результат второй задачи
        
        println("Сумма: ${result1 + result2}")
    }
    println("Выполнено за $time мс")
}

suspend fun fetchData1(): Int {
    delay(1000) // Имитация долгой операции
    return 10
}

suspend fun fetchData2(): Int {
    delay(1500) // Имитация долгой операции
    return 20
}

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

  1. Параллельное выполнение

    • Когда вы запускаете несколько async-корутин, они выполняются конкурентно (параллельно, если позволяет диспетчер)
    • В примере выше fetchData1() и fetchData2() запускаются практически одновременно
  2. Ленивый запуск (lazy mode)

    val deferred = async(start = CoroutineStart.LAZY) {
        // Этот код не запустится автоматически
        expensiveComputation()
    }
    
    // Запуск только при явном вызове start() или await()
    deferred.start() // или deferred.await()
    
  3. Структурированный параллелизм

    coroutineScope {
        val deferred1 = async { task1() }
        val deferred2 = async { task2() }
        
        // Если здесь произойдёт исключение, ВСЕ дочерние корутины будут отменены
        val results = listOf(deferred1, deferred2).awaitAll()
    }
    

Отличия от launch

// launch - для "fire-and-forget" задач (без возвращаемого значения)
val job = launch {
    // делаем что-то фоновое
}

// async - для задач с возвращаемым значением
val deferred = async {
    // вычисляем значение
    return@async 42
}
val result = deferred.await()

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

Критически важный момент: исключения в async-корутине НЕ пробрасываются автоматически в родительскую корутину до вызова await():

try {
    val deferred = async {
        throw RuntimeException("Ошибка в async!")
    }
    // Исключение произойдёт только здесь, при вызове await()
    deferred.await()
} catch (e: Exception) {
    println("Поймано: ${e.message}")
}

Для немедленной отмены родительской корутины при ошибке в async используйте SupervisorJob или supervisorScope.

Диспетчеры и async

// Запуск в IO-диспетчере для сетевых операций
val data = async(Dispatchers.IO) {
    fetchFromNetwork()
}

// Запуск в Default для CPU-интенсивных задач
val result = async(Dispatchers.Default) {
    computeHeavyAlgorithm()
}

Практические паттерны

  1. Параллельная загрузка данных

    suspend fun loadUserData(userId: String): UserData = coroutineScope {
        val profileDeferred = async { api.getProfile(userId) }
        val friendsDeferred = async { api.getFriends(userId) }
        val postsDeferred = async { api.getPosts(userId) }
        
        UserData(
            profile = profileDeferred.await(),
            friends = friendsDeferred.await(),
            posts = postsDeferred.await()
        )
    }
    
  2. Ограничение параллелизма

    val results = listOfUrls.map { url ->
        async { downloadFile(url) }
    }.awaitAll() // Все задачи запустятся параллельно
    

Производительность и предостережения

  • Не создавайте чрезмерное количество async-корутин для мелких задач — накладные расходы могут превысить выгоду
  • Всегда явно обрабатывайте исключения из async-корутин
  • Используйте awaitAll() для ожидания нескольких Deferred:
    val deferredList = listOf(async { task1() }, async { task2() })
    val results = deferredList.awaitAll()
    

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

Как работает async вместе с await в корутинах? | PrepBro