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 та використовується для завдань, що повертають дані. За синтаксисом та параметрами білдери ідентичні.
Важливе правило: ніколи не використовуйте async для 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, прив’язана до області видимості. GlobalScope.async створює корутину без прив’язки до життєвого циклу — вона може виконуватися необмежено. GlobalScope не рекомендується в Android через ризик витоків.
Кількість визначається диспетчером: Dispatchers.IO підтримує до 64 одночасних потоків, Dispatchers.Default — до кількості ядер CPU. Для масових операцій використовуйте обмежений паралелізм через Semaphore або mapNotNull з awaitAll.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також