async — è un Costruttore di Coroutine in Kotlin che avvia una coroutine e restituisce un oggetto Deferred<T> per ottenere il risultato di un’operazione asincrona. async consente di eseguire più attività in parallelo e aggregare i loro risultati tramite await(). Secondo la guida JetBrains, 2024, async è il metodo preferito per organizzare calcoli paralleli nelle coroutine.
Punti Chiave
async — è una funzione di estensione di CoroutineScope che crea una coroutine restituendo un risultato. A differenza di launch, async restituisce Deferred<T> — un oggetto che rappresenta un valore futuro. Una coroutine avviata tramite async viene eseguita concorrentemente con altre coroutine, consentendo di parallelizzare i calcoli.
async viene utilizzato quando il risultato di un’operazione asincrona è necessario per l’elaborazione successiva: caricamento di dati da più fonti, elaborazione in batch di collezioni, richieste parallele a varie API. Deferred viene creato immediatamente e il risultato diventa disponibile dopo il completamento della 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 firma di async è simile a launch: gli stessi parametri CoroutineContext, CoroutineStart e un blocco suspend. La differenza sta nel tipo di ritorno — Deferred<T> invece di Job, dove T è il tipo di risultato restituito dall’ultima espressione del blocco.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — un’interfaccia che eredita Job e aggiunge i metodi await(), getCompleted(), getCompletionExceptionOrNull(). await() è una funzione suspend e può essere chiamata solo all’interno di una coroutine o di un’altra funzione suspend. getCompleted() è un metodo non bloccante che lancia un’eccezione se la coroutine non è ancora stata completata.
Il principale vantaggio di async è la capacità di eseguire attività in parallelo. Invece di attendere ogni operazione sequenzialmente (tempo totale = somma dei tempi), async avvia le coroutine simultaneamente, riducendo il tempo totale alla durata dell’operazione più lunga.
Uno scenario tipico — caricare il profilo utente, le sue impostazioni e la cronologia ordini con tre richieste async parallele. Dopo il completamento di tutte e tre, i risultati vengono combinati in un unico modello di dati.
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 funzione coroutineScope avvia coroutine figlie e attende il loro completamento prima di restituire il risultato. L’intero blocco viene eseguito in parallelo, non sequenzialmente.
Deferred supporta diversi metodi per lavorare con il risultato. Oltre a await(), ci sono metodi per controllare lo stato, ottenere un valore completato e aggregare più Deferred tramite le funzioni di utilità di Kotlin Coroutines.
| Metodo | Descrizione | Suspend? |
|---|---|---|
| await() | Sospende la coroutine fino all’ottenimento del risultato | Sì |
| getCompleted() | Restituisce il risultato se la coroutine è completata (altrimenti Exception) | No |
| getCompletionExceptionOrNull() | Restituisce l’eccezione in caso di errore o null | No |
| isCompleted | Verifica se la coroutine è completata | No |
Per aggregare una lista di Deferred, si usa awaitAll() — una funzione che sospende l’esecuzione fino al completamento di tutti i Deferred nella collezione. Se almeno uno si è completato con errore, awaitAll() lancia l’eccezione.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
La scelta tra async e launch dipende dal fatto che il risultato della coroutine sia necessario. launch restituisce Job ed è adatto per operazioni fire-and-forget, async restituisce Deferred ed è usato per attività che restituiscono dati. In termini di sintassi e parametri, i costruttori sono identici.
Regola importante: non usare mai async per fire-and-forget. Se il risultato della coroutine non è necessario, usa launch. async crea un overhead per la creazione di Deferred, che non è giustificato in tali scenari.
In Android, async viene utilizzato per operazioni parallele all’interno di ViewModel e UseCases. Scenari tipici: caricare profilo e feed notizie simultaneamente, ottenere meteo da più servizi meteorologici, sincronizzazione batch dei dati con il server.
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 }
}
}
}
Si raccomanda di avvolgere un gruppo di async in coroutineScope o supervisorScope per isolare gli errori. supervisorScope consente alle restanti coroutine async di continuare anche se una fallisce — utile per operazioni non critiche.
Domande Frequenti
Si può, ma è inutile. async senza await() avvia una coroutine ma il suo risultato viene perso. Se il risultato non è necessario, usa launch — non crea Deferred ed è più efficiente in termini di memoria e prestazioni.
Un’eccezione all’interno di async viene memorizzata in Deferred. Chiamare await() lancia questa eccezione. Per verificare senza lanciare, usa getCompletionExceptionOrNull(). Quando si utilizza coroutineScope, un errore annulla tutte le coroutine figlie.
awaitAll() — una funzione di estensione per Iterable<Deferred<T>> che sospende la coroutine fino al completamento di tutti i Deferred. Restituisce una lista di risultati. Se almeno un Deferred fallisce, lancia l’eccezione e cancella gli altri.
Sì. async è una funzione di estensione di CoroutineScope legata allo scope. GlobalScope.async crea una coroutine senza legame con il ciclo di vita — può eseguire indefinitamente. GlobalScope non è raccomandato in Android a causa del rischio di perdite.
Il numero è dettato dal dispatcher: Dispatchers.IO supporta fino a 64 thread simultanei, Dispatchers.Default — fino al numero di core CPU. Per operazioni massive, usa parallelismo limitato tramite Semaphore o mapNotNull con awaitAll.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche