async — Coroutine Builder în Kotlin care lansează o corutină și returnează un obiect Deferred<T> pentru obținerea rezultatului unei operații asincrone. async permite executarea paralelă a mai multor sarcini și agregarea rezultatelor lor prin await(). Conform ghidului JetBrains, 2024, async este metoda preferată de organizare a calculelor paralele în corutine.
Principalele
async — este o funcție de extensie CoroutineScope care creează o corutină cu returnare de rezultat. Spre deosebire de launch, async returnează Deferred<T> — un obiect care reprezintă o valoare viitoare. Corutina lansată prin async se execută concurent cu alte corutine, permițând paralelizarea calculelor.
async este utilizat când rezultatul operației asincrone este necesar pentru procesare ulterioară: încărcarea datelor din mai multe surse, procesarea în lot a colecțiilor, cereri paralele la diverse API-uri. Deferred este creat imediat, iar rezultatul devine disponibil după finalizarea corutinei.
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())
}
Semnătura async este similară cu launch: aceiași parametri CoroutineContext, CoroutineStart și bloc suspend. Diferența constă în tipul returnat — Deferred<T> în loc de Job, unde T este tipul rezultatului returnat de ultima expresie a blocului.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — interfață care moștenește Job și adaugă metodele await(), getCompleted(), getCompletionExceptionOrNull(). await() este o funcție suspend și poate fi apelată doar în interiorul unei corutine sau altei funcții suspend. getCompleted() — metodă neblocantă care aruncă o excepție dacă corutina nu s-a încheiat încă.
Principalul avantaj al async este posibilitatea de a lansa sarcini în paralel. În loc de așteptarea secvențială a fiecărei operații (timp total = suma timpurilor), async lansează corutinele simultan, reducând timpul total la timpul celei mai lungi operații.
Scenariu tipic — încărcarea profilului utilizatorului, setărilor și istoricului comenzilor prin trei cereri async paralele. După finalizarea tuturor celor trei, rezultatele sunt combinate într-un singur model de date.
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()
)
}
Funcția coroutineScope lansează corutinele copil și așteaptă finalizarea lor înainte de a returna rezultatul. Întregul bloc se execută paralel, nu secvențial.
Deferred suportă mai multe metode pentru lucrul cu rezultatul. Pe lângă await(), sunt disponibile metode pentru verificarea stării, obținerea valorii gata și agregarea mai multor Deferred prin funcțiile utilitare Kotlin Coroutines.
| Metodă | Descriere | Suspend? |
|---|---|---|
| await() | Întrerupe corutina până la obținerea rezultatului | Da |
| getCompleted() | Returnează rezultatul dacă corutina s-a încheiat (altfel Exception) | Nu |
| getCompletionExceptionOrNull() | Returnează excepția la eroare sau null | Nu |
| isCompleted | Verifică dacă corutina s-a încheiat | Nu |
Pentru agregarea unei liste de Deferred se folosește awaitAll() — o funcție care întrerupe execuția până la finalizarea tuturor Deferred din colecție. Dacă cel puțin unul s-a încheiat cu eroare, awaitAll() aruncă o excepție.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
Alegerea între async și launch depinde de necesitatea rezultatului execuției corutinei. launch returnează Job și este potrivit pentru operații fire-and-forget, async returnează Deferred și este folosit pentru sarcini care returnează date. Din punct de vedere al sintaxei și parametrilor, constructorii sunt identici.
Regulă importantă: nu folosiți async pentru fire-and-forget. Dacă rezultatul corutinei nu este necesar, utilizați launch. async creează un overhead pentru crearea Deferred, care nu este justificat într-un astfel de scenariu.
În Android, async este utilizat pentru operații paralele în ViewModel și UseCases. Scenarii tipice: încărcarea simultană a profilului și a fluxului de știri, obținerea vremii de la mai multe servicii meteo, sincronizarea în lot a datelor cu serverul.
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 }
}
}
}
Se recomandă încapsularea grupului async în coroutineScope sau supervisorScope pentru izolarea erorilor. supervisorScope permite continuarea execuției celorlalte corutine async chiar și la eroare într-una dintre ele — util pentru operații necritice.
Întrebări frecvente
Se poate, dar e inutil. async fără await() lansează corutina, dar rezultatul ei se pierde. Dacă rezultatul nu este necesar, utilizați launch — nu creează Deferred și este mai eficient din punct de vedere al memoriei și performanței.
Excepția din interiorul async este salvată în Deferred. Apelul await() propagă această excepție. Pentru verificare fără propagare, utilizați getCompletionExceptionOrNull(). La utilizarea coroutineScope, eroarea anulează toate corutinele copil.
awaitAll() — o funcție de extensie pentru Iterable<Deferred<T>> care întrerupe corutina până la finalizarea tuturor Deferred. Returnează o listă de rezultate. Dacă cel puțin un Deferred se încheie cu eroare, propagă excepția și anulează restul.
Da. async este o funcție de extensie CoroutineScope, legată de scope. GlobalScope.async creează o corutină fără legătură cu ciclul de viață — se poate executa la nesfârșit. În Android, GlobalScope nu este recomandat din cauza riscului de scurgeri de memorie.
Numărul este dictat de dispatcher: Dispatchers.IO suportă până la 64 de fire simultane, Dispatchers.Default — după numărul de nuclee CPU. Pentru operații masive, utilizați paralelism limitat prin Semaphore sau mapNotNull cu awaitAll.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și