async — Coroutine Builder в Kotlin, который запускает сопрограмму и возвращает объект Deferred<T> для получения результата асинхронной операции. async позволяет выполнять несколько задач параллельно и агрегировать их результаты через await(). По данным руководства JetBrains, 2024, async является предпочтительным способом организации параллельных вычислений в корутинах.
Главное
async — это функция-расширение CoroutineScope, создающая сопрограмму с возвратом результата. В отличие от launch, async возвращает Deferred<T> — объект, который представляет будущее значение. Корутина, запущенная через async, выполняется конкурентно с другими корутинами, что позволяет распараллеливать вычисления.
async используется, когда результат асинхронной операции нужен для дальнейшей обработки: загрузка данных из нескольких источников, пакетная обработка коллекций, параллельные запросы к различным API. Deferred создаётся немедленно, а результат становится доступным после завершения корутины.
import kotlinx.coroutines.*
suspend fun String.delayedValue(): String {
delay(1000L)
return this
}
fun main() = runBlocking {
val deferred: Deferred<String> = async { "Hello".delayedValue() }
println(deferred.await())
}
Сигнатура async аналогична launch: те же параметры CoroutineContext, CoroutineStart и suspend-блок. Различие в возвращаемом типе — Deferred<T> вместо Job, где T — тип результата, возвращаемого последним выражением блока.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — интерфейс, наследующий Job и добавляющий методы await(), getCompleted(), getCompletionExceptionOrNull(). await() является suspend-функцией и может быть вызван только внутри корутины или другой suspend-функции. getCompleted() — неблокирующий метод, выбрасывающий исключение, если корутина ещё не завершена.
Основное преимущество async — возможность запускать задачи параллельно. Вместо последовательного ожидания каждой операции (общее время = сумма времён), async запускает корутины одновременно, сокращая общее время до времени самой долгой операции.
Типовой сценарий — загрузка профиля пользователя, его настроек и истории заказов тремя параллельными async-запросами. После завершения всех трёх результаты объединяются в единую модель данных.
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
val profile = async { api.getProfile(userId) }
val settings = async { api.getSettings(userId) }
val orders = async { api.getRecentOrders(userId) }
Dashboard(
profile = profile.await(),
settings = settings.await(),
orders = orders.await()
)
}
Функция coroutineScope запускает дочерние корутины и ожидает их завершения перед возвратом результата. При этом весь блок выполняется параллельно, а не последовательно.
Deferred поддерживает несколько методов для работы с результатом. Помимо await(), доступны методы для проверки статуса, получения готового значения и агрегации нескольких Deferred через функции-утилиты Kotlin Coroutines.
| Метод | Описание | Suspend? |
|---|---|---|
| await() | Приостанавливает корутину до получения результата | Да |
| getCompleted() | Возвращает результат, если корутина завершена (инасе Exception) | Нет |
| getCompletionExceptionOrNull() | Возвращает исключение при ошибке или null | Нет |
| isCompleted | Проверяет, завершена ли корутина | Нет |
Для агрегации списка Deferred используется awaitAll() — функция, приостанавливающая выполнение до завершения всех Deferred в коллекции. Если хотя бы один завершился с ошибкой, awaitAll() пробрасывает исключение.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
Выбор между async и launch зависит от того, нужен ли результат выполнения корутины. launch возвращает Job и подходит для операций fire-and-forget, async возвращает Deferred и используется для задач, возвращающих данные. По синтаксису и параметрам билдеры идентичны.
Важное правило: never use async for fire-and-forget. Если результат корутины не нужен, используйте launch. async создаёт накладные расходы на создание Deferred, которые в таком сценарии не оправданы.
В Android async применяется для параллельных операций внутри ViewModel и UseCases. Типовые сценарии: загрузка профиля и ленты новостей одновременно, получение погоды с нескольких метеосервисов, пакетная синхронизация данных с сервером.
class HomeViewModel : ViewModel() {
fun loadHomeScreen() {
viewModelScope.launch {
val userData = async(Dispatchers.IO) { repository.getUserData() }
val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
val notifications = async(Dispatchers.IO) { repository.getNotifications() }
val state = HomeState(
user = userData.await(),
news = newsFeed.await(),
unread = notifications.await()
)
_uiState.update { state }
}
}
}
Рекомендуется оборачивать группу async в coroutineScope или supervisorScope для изоляции ошибок. supervisorScope позволяет продолжить выполнение остальных async-корутин даже при ошибке в одной из них — полезно для некритичных операций.
Часто задаваемые вопросы
Можно, но бессмысленно. async без await() запускает корутину, но её результат теряется. Если результат не нужен, используйте launch — он не создаёт Deferred и эффективнее по памяти и производительности.
Исключение внутри async сохраняется в Deferred. Вызов await() пробрасывает это исключение. Для проверки без проброса используйте getCompletionExceptionOrNull(). При использовании coroutineScope ошибка отменяет все дочерние корутины.
awaitAll() — функция-расширение для Iterable<Deferred<T>>, приостанавливающая корутину до завершения всех Deferred. Возвращает список результатов. При ошибке хотя бы одного Deferred пробрасывает исключение и отменяет остальные.
Да. async — функция-расширение CoroutineScope, привязанная к scope. GlobalScope.async создаёт корутину без привязки к жизненному циклу — она может выполняться бесконечно. В Android GlobalScope не рекомендуется из-за риска утечек.
Количество диктуется диспетчером: Dispatchers.IO поддерживает до 64 одновременных потоков, Dispatchers.Default — по числу CPU-ядер. Для массовых операций используйте ограниченный параллелизм через Semaphore или mapNotNull с awaitAll.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также