Kotlin CoroutinesMiddleTechnical
Что такое Deferred<T> и как использовать await()?
Deferred<T> — результат async-корутины; await() приостанавливает вызывающую корутину до получения значения, не блокируя поток. Несколько Deferred запускают задачи параллельно, а awaitAll() ждёт их все сразу.
Deferred<T> — отложённый результат корутины
Deferred<T> — это интерфейс из пакета kotlinx.coroutines, расширяющий Job. Он представляет корутину, которая в итоге вернёт значение типа T. Создаётся строителем async { ... }, который немедленно запускает корутину в фоне, а не блокирует поток.
Ключевые методы
await()— suspend-функция, приостанавливает текущую корутину до получения результата. Если корутина завершилась исключением, оно пробрасывается в вызывающем месте.getCompleted()— возвращает результат без suspend; бросает исключение, если корутина ещё не завершилась.isCompleted,isCancelled,isActive— свойства состояния, аналогичныеJob.cancel()— отменяет корутину, не ожидая результата.
Базовый пример
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred: Deferred<Int> = async {
delay(500)
42
}
println("Waiting...")
val result = deferred.await() // приостанавливается, не блокирует поток
println("Result: $result") // Result: 42
}
Параллельная декомпозиция
Главная ценность async/await — запускать несколько независимых задач параллельно и потом объединять их результаты.
suspend fun fetchUserAndOrders(userId: String): Pair<User, List<Order>> =
coroutineScope { // структурированный контекст
val userDeferred = async { userRepo.find(userId) }
val ordersDeferred = async { orderRepo.findByUser(userId) }
userDeferred.await() to ordersDeferred.await()
}
Здесь оба вызова БД идут параллельно. Общее время — max(t1, t2), а не t1 + t2.
await() с таймаутом
val result = withTimeout(2000L) {
deferred.await() // бросает TimeoutCancellationException при превышении
}
awaitAll для коллекций
val deferreds: List<Deferred<Int>> = ids.map { id ->
async { fetchScore(id) }
}
val scores: List<Int> = deferreds.awaitAll() // extension из kotlinx.coroutines
Ленивый запуск
val lazy = async(start = CoroutineStart.LAZY) {
heavyComputation()
}
// корутина запускается только при вызове await() или start()
lazy.start() // явный запуск без ожидания
val v = lazy.await() // или запуск + ожидание
Подводные камни
- Потеря исключения без await. Если не вызвать
await(), исключение внутриasync-блока не распространится автоматически — оно «зависнет» вDeferredи проявится только при вызовеawait(). Это отличие отlaunch, где исключение сразу идёт вCoroutineExceptionHandler. - Вызов await() вне coroutine scope.
await()— suspend-функция; вызов из обычного кода не скомпилируется. НуженrunBlockingили другой coroutine builder. - async вне coroutineScope. Запуск
asyncнапрямую в GlobalScope лишает структурированного параллелизма: утечки корутин, невозможность отмены родителем. - Последовательный await вместо параллельного.
val a = async { ... }.await(); val b = async { ... }.await()— это последовательно. Нужно сначала создать обаDeferred, потом обаawait(). - Игнорирование отмены. Если вызвать
deferred.cancel(), а потомawait(), будет брошеноCancellationException. Обрабатывайте его явно или используйтеrunCatching. - Неправильная обработка ошибок при awaitAll. Если один из
Deferredв списке упал,awaitAll()отменяет остальные и пробрасывает исключение первого упавшего.
Common mistakes
- Объяснять «Deferred
и await()» только как синтаксис и не описывать поведение runtime/compiler. - Игнорировать важный риск: Повторный await безопасен для результата, но ленивые Deferred и забытый await делают поток выполнения неочевидным.
- Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.
What the interviewer is testing
- Формулирует суть темы «Deferred
и await()» своими словами и связывает ее с кодом. - Называет механизм: await suspend-ится до завершения Deferred и либо возвращает T, либо выбрасывает сохраненное исключение.
- Видит production-последствие: Повторный await безопасен для результата, но ленивые Deferred и забытый await делают поток выполнения неочевидным.