async: какво е това, Deferred и паралелни корутини как работят

Автор: IT Sectr Публикувано: 2026-06-22 Време за четене: 8 мин

async — Coroutine Builder в Kotlin, който стартира корутина и връща обект Deferred<T> за получаване на резултата от асинхронна операция. async позволява паралелно изпълнение на няколко задачи и агрегиране на резултатите им чрез await(). Според ръководството на JetBrains, 2024, async е предпочитаният начин за организиране на паралелни изчисления в корутини.

Основни точки

  • async — строител на корутини, връщащ Deferred<T> с резултата от асинхронна операция
  • Deferred — асинхронно обещание, наследяващо Job, с метод await() за получаване на резултат
  • async позволява паралелно изпълнение на независими задачи, намалявайки общото време
  • await() — suspend-функция, която спира корутината до получаване на резултат без блокиране
  • Структурирана конкурентност с async гарантира отмяна на всички паралелни задачи при грешка

Какво е async в Kotlin Coroutines?

async — е разширяваща функция на CoroutineScope, която създава корутина с връщане на резултат. За разлика от launch, async връща Deferred<T> — обект, представляващ бъдеща стойност. Корутината, стартирана чрез async, се изпълнява конкурентно с други корутини, което позволява паралелизиране на изчисленията.

async се използва, когато резултатът от асинхронна операция е необходим за по-нататъшна обработка: зареждане на данни от множество източници, пакетна обработка на колекции, паралелни заявки към различни API-та. Deferred се създава незабавно, а резултатът става достъпен след завършване на корутината.

kotlin
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 и Deferred

Подписът на async е подобен на launch: същите параметри CoroutineContext, CoroutineStart и suspend-блок. Разликата е в връщания тип — Deferred<T> вместо Job, където T е типът на резултата, върнат от последния израз на блока.

kotlin
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 стартира корутините едновременно, намалявайки общото време до времето на най-дългата операция.

Паралелно зареждане на данни

Типичен сценарий — зареждане на потребителски профил, неговите настройки и история на поръчките с три паралелни async заявки. След завършване на трите, резултатите се комбинират в единен модел данни.

kotlin
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

Deferred поддържа няколко метода за работа с резултата. Освен await(), налични са методи за проверка на статуса, получаване на готова стойност и агрегиране на множество Deferred чрез помощни функции на Kotlin Coroutines.

МетодОписаниеSuspend?
await()Спира корутината до получаване на резултатаДа
getCompleted()Връща резултата, ако корутината е завършила (иначе Exception)Не
getCompletionExceptionOrNull()Връща изключението при грешка или nullНе
isCompletedПроверява дали корутината е завършилаНе

За агрегиране на списък от Deferred се използва awaitAll() — функция, която спира изпълнението докато всички Deferred в колекцията завършат. Ако поне един е завършил с грешка, awaitAll() хвърля изключение.

kotlin
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
    return coroutineScope {
        val deferreds = requests.map { async { it() } }
        deferreds.awaitAll()
    }
}

Разлики между async и launch

Изборът между async и launch зависи от това дали резултатът от изпълнението на корутината е необходим. launch връща Job и е подходящ за fire-and-forget операции, async връща Deferred и се използва за задачи, връщащи данни. По отношение на синтаксис и параметри, строителите са идентични.

  • launch → Job (без резултат), async → Deferred<T> (има резултат T)
  • launch — fire-and-forget задачи (логване, кеширане, изпращане на аналитика)
  • async — паралелни изчисления (зареждане от няколко API-та, пакетна обработка)
  • И двата поддържат структурирана конкурентност и параметри CoroutineStart
  • await() е достъпен само за Deferred, launch не предоставя резултат

Важно правило: никога не използвайте async за fire-and-forget. Ако резултатът от корутината не е необходим, използвайте launch. async създава допълнително натоварване за създаване на Deferred, което не е оправдано в такъв сценарий.

Примери за async в мобилната разработка

В Android async се използва за паралелни операции вътре в ViewModel и UseCases. Типични сценарии: едновременно зареждане на профил и новинарски поток, получаване на времето от няколко метеорологични услуги, пакетна синхронизация на данни със сървъра.

kotlin
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?

Може, но е безсмислено. async без await() стартира корутината, но резултатът ѝ се губи. Ако резултатът не е необходим, използвайте launch — той не създава Deferred и е по-ефективен по отношение на памет и производителност.

Как async обработва изключения?

Изключението вътре в async се запазва в Deferred. Извикването на await() прехвърля това изключение. За проверка без прехвърляне използвайте getCompletionExceptionOrNull(). При използване на coroutineScope грешката отменя всички дъщерни корутини.

Какво е awaitAll в Kotlin Coroutines?

awaitAll() — разширяваща функция за Iterable<Deferred<T>>, която спира корутината докато всички Deferred завършат. Връща списък с резултати. Ако поне един Deferred завърши с грешка, прехвърля изключението и отменя останалите.

Различава ли се async от GlobalScope.async?

Да. async е разширяваща функция на CoroutineScope, обвързана с обхвата. GlobalScope.async създава корутина без обвързване с жизнения цикъл — може да се изпълнява безкрайно. В Android GlobalScope не се препоръчва поради риск от изтичане на памет.

Колко async корутини могат да бъдат стартирани за паралелни заявки?

Броят се диктува от диспечера: Dispatchers.IO поддържа до 64 едновременни нишки, Dispatchers.Default — според броя на CPU ядрата. За масови операции използвайте ограничен паралелизъм чрез Semaphore или mapNotNull с awaitAll.

Резюме

  • async — строител на корутини, връщащ Deferred<T> за асинхронно получаване на резултат
  • Deferred наследява Job и добавя методите await(), getCompleted(), getCompletionExceptionOrNull()
  • Паралелно изпълнение с async намалява общото време до продължителността на най-дългата операция
  • awaitAll() — инструмент за изчакване на всички Deferred в колекция
  • coroutineScope — препоръчителна обвивка за група async с гаранция за отмяна
  • Не използвайте async за fire-and-forget — приложете launch
  • В Android async се използва за паралелно зареждане в ViewModel с viewModelScope

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също