async — est un constructeur de coroutines en Kotlin qui lance une coroutine et retourne un objet Deferred<T> pour obtenir le résultat d’une opération asynchrone. async permet d’exécuter plusieurs tâches en parallèle et d’agréger leurs résultats via await(). Selon le guide JetBrains, 2024, async est la méthode préférée pour organiser les calculs parallèles dans les coroutines.
Points clés
async — est une fonction d’extension de CoroutineScope qui crée une coroutine retournant un résultat. Contrairement à launch, async retourne Deferred<T> — un objet représentant une valeur future. Une coroutine lancée via async s’exécute concurremment avec d’autres coroutines, permettant de paralléliser les calculs.
async est utilisé lorsque le résultat d’une opération asynchrone est nécessaire pour un traitement ultérieur : chargement de données depuis plusieurs sources, traitement par lots de collections, requêtes parallèles à diverses API. Deferred est créé immédiatement, et le résultat est disponible après l’achèvement de la coroutine.
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())
}
La signature de async est similaire à launch : les mêmes paramètres CoroutineContext, CoroutineStart et un bloc suspend. La différence réside dans le type de retour — Deferred<T> au lieu de Job, où T est le type de résultat retourné par la dernière expression du bloc.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — une interface héritant de Job et ajoutant les méthodes await(), getCompleted(), getCompletionExceptionOrNull(). await() est une fonction suspend et ne peut être appelée qu’à l’intérieur d’une coroutine ou d’une autre fonction suspend. getCompleted() est une méthode non bloquante qui lève une exception si la coroutine n’est pas encore terminée.
Le principal avantage de async est la possibilité d’exécuter des tâches en parallèle. Au lieu d’attendre chaque opération séquentiellement (temps total = somme des temps), async lance les coroutines simultanément, réduisant le temps total à la durée de l’opération la plus longue.
Un scénario typique — charger le profil de l’utilisateur, ses paramètres et son historique de commandes avec trois requêtes async parallèles. Après l’achèvement des trois, les résultats sont combinés en un seul modèle de données.
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()
)
}
La fonction coroutineScope lance des coroutines filles et attend leur achèvement avant de retourner le résultat. L’ensemble du bloc s’exécute en parallèle, pas séquentiellement.
Deferred prend en charge plusieurs méthodes pour travailler avec le résultat. En plus de await(), il existe des méthodes pour vérifier l’état, obtenir une valeur terminée et agréger plusieurs Deferred via les fonctions utilitaires de Kotlin Coroutines.
| Méthode | Description | Suspend ? |
|---|---|---|
| await() | Suspend la coroutine jusqu’à l’obtention du résultat | Oui |
| getCompleted() | Retourne le résultat si la coroutine est terminée (sinon Exception) | Non |
| getCompletionExceptionOrNull() | Retourne l’exception en cas d’erreur ou null | Non |
| isCompleted | Vérifie si la coroutine est terminée | Non |
Pour agréger une liste de Deferred, on utilise awaitAll() — une fonction qui suspend l’exécution jusqu’à ce que tous les Deferred de la collection soient terminés. Si au moins un s’est terminé avec une erreur, awaitAll() lève l’exception.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
Le choix entre async et launch dépend de la nécessité du résultat de la coroutine. launch retourne Job et convient aux opérations fire-and-forget, async retourne Deferred et est utilisé pour les tâches qui retournent des données. En termes de syntaxe et de paramètres, les constructeurs sont identiques.
Règle importante : n’utilisez jamais async pour du fire-and-forget. Si le résultat de la coroutine n’est pas nécessaire, utilisez launch. async crée une surcharge en créant Deferred, ce qui n’est pas justifié dans de tels scénarios.
Dans Android, async est utilisé pour les opérations parallèles au sein de ViewModel et UseCases. Scénarios typiques : charger le profil et le fil d’actualité simultanément, obtenir la météo depuis plusieurs services météorologiques, synchronisation par lots des données avec le serveur.
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 }
}
}
}
Il est recommandé d’encapsuler un groupe de async dans coroutineScope ou supervisorScope pour isoler les erreurs. supervisorScope permet aux autres coroutines async de continuer même si l’une échoue — utile pour les opérations non critiques.
Foire aux questions
On peut, mais c’est inutile. async sans await() lance une coroutine mais son résultat est perdu. Si le résultat n’est pas nécessaire, utilisez launch — il ne crée pas Deferred et est plus efficace en mémoire et en performances.
Une exception dans async est stockée dans Deferred. L’appel à await() lève cette exception. Pour vérifier sans lever, utilisez getCompletionExceptionOrNull(). Lors de l’utilisation de coroutineScope, une erreur annule toutes les coroutines filles.
awaitAll() — une fonction d’extension pour Iterable<Deferred<T>> qui suspend la coroutine jusqu’à ce que tous les Deferred soient terminés. Retourne une liste de résultats. Si au moins un Deferred échoue, elle lève l’exception et annule les autres.
Oui. async est une fonction d’extension de CoroutineScope liée à la portée. GlobalScope.async crée une coroutine sans liaison au cycle de vie — elle peut s’exécuter indéfiniment. GlobalScope n’est pas recommandé dans Android en raison du risque de fuites.
Le nombre est dicté par le dispatcher : Dispatchers.IO supporte jusqu’à 64 threads simultanés, Dispatchers.Default — jusqu’au nombre de cœurs CPU. Pour les opérations massives, utilisez un parallélisme limité via Semaphore ou mapNotNull avec awaitAll.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi