Coroutine Builder — funzioni di Kotlin Coroutines che creano e avviano coroutine, definendo come vengono eseguite. I costruttori launch, async, runBlocking e produce coprono diversi scenari: dalle attività in background ai calcoli paralleli con restituzione del risultato. Secondo JetBrains, 2024, Coroutine Builder è il fondamento del modello a coroutine, fornendo concorrenza strutturata e gestione del ciclo di vita.
Punti chiave
Coroutine Builder è una funzione di estensione di Kotlin che accetta un CoroutineScope e un blocco suspend, creando e avviando una nuova coroutine. Ogni costruttore definisce come la coroutine verrà eseguita: con o senza restituzione del risultato, con blocco del thread o in modo asincrono. I costruttori sono i punti di ingresso nel modello a coroutine del linguaggio.
Tutti i costruttori lavorano tramite CoroutineScope, che gestisce il ciclo di vita delle coroutine figlie. Quando un ambito viene annullato, tutte le coroutine avviate tramite esso vengono automaticamente annullate — questo è il principio della concorrenza strutturata. Questo approccio previene perdite di coroutine e garantisce un termine prevedibile.
import kotlinx.coroutines.*
fun main() = runBlocking {
// I costruttori vengono eseguiti all'interno di CoroutineScope
val job = launch {
delay(1000L)
println("Mondo!")
}
println("Ciao,")
job.join()
}
Kotlin fornisce quattro costruttori di coroutine integrati: launch, async, runBlocking e produce. Ciascuno ha il proprio tipo di restituzione e area di applicazione. Per lo sviluppo mobile Android, i costruttori principali sono launch e async — funzionano in modo non bloccante e si integrano con i componenti architetturali.
| Costruttore | Tipo restituito | Blocco thread | Scenario |
|---|---|---|---|
| launch | Job | No | Attività fire-and-forget |
| async | Deferred<T> | No | Calcoli paralleli |
| runBlocking | T | Sì | Test, funzione main |
| produce | ReceiveChannel<E> | No | Streaming (deprecato) |
Ogni costruttore accetta parametri aggiuntivi: CoroutineStart (strategia di avvio), CoroutineContext (dispatcher, eccezioni) e un blocco di codice denominato. Per impostazione predefinita, la coroutine viene avviata immediatamente (CoroutineStart.DEFAULT).
launch è il costruttore più utilizzato nello sviluppo Android. Avvia una coroutine che non restituisce un risultato e restituisce un oggetto Job per gestirne il ciclo di vita. È la scelta ideale per operazioni che necessitano solo di un effetto collaterale: salvataggio nel database, invio di analisi, aggiornamento dell'interfaccia.
Il costruttore launch accetta CoroutineScope, un CoroutineContext opzionale e un blocco suspend. Il Job restituito consente di annullare la coroutine, attendere il completamento o verificarne lo stato.
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val job: Job = scope.launch(CoroutineStart.LAZY) {
val data = fetchFromNetwork()
saveToDatabase(data)
}
job.start()
job.join()
Il parametro CoroutineStart.LAZY posticipa l'esecuzione fino a una chiamata esplicita a start() o join(). Ciò è utile per l'inizializzazione ritardata e l'esecuzione condizionale. Per l'esecuzione immediata standard si usa CoroutineStart.DEFAULT o si omette il parametro.
async è un costruttore che restituisce Deferred<T> — una promessa asincrona di un risultato. La chiamata a await() sospende la coroutine fino all'ottenimento del risultato, senza bloccare il thread. Questo è il meccanismo principale per le attività parallele nelle coroutine Kotlin.
async è particolarmente efficace quando è necessario eseguire più operazioni indipendenti simultaneamente. A differenza delle chiamate sequenziali a funzioni suspend, async avvia le coroutine in parallelo, riducendo il tempo di esecuzione totale.
suspend fun fetchUserData(): UserData {
val deferred1 = CoroutineScope(Dispatchers.IO).async { api.getProfile() }
val deferred2 = CoroutineScope(Dispatchers.IO).async { api.getSettings() }
val deferred3 = CoroutineScope(Dispatchers.IO).async { api.getNotifications() }
return UserData(
profile = deferred1.await(),
settings = deferred2.await(),
notifications = deferred3.await()
)
}
Deferred eredita da Job, quindi async supporta tutte le operazioni del ciclo di vita: annullamento, attesa del completamento, gestione delle eccezioni. Quando un ambito viene annullato, le coroutine Deferred figlie vengono automaticamente annullate.
runBlocking è l'unico costruttore che blocca il thread corrente fino al completamento della coroutine. Crea un nuovo CoroutineScope e avvia la coroutine passata, bloccando il thread chiamante. Viene utilizzato nei punti di ingresso main(), nei test e nell'integrazione con codice bloccante.
runBlocking è giustificato in tre scenari: il punto di ingresso dell'applicazione (main), i test unitari di funzioni suspend e l'integrazione con librerie basate su callback dove suspend non può essere utilizzato. Nel codice Android di produzione, l'uso di runBlocking sul thread principale non è raccomandato.
class CoroutineTest {
@Test
fun `test suspend function`() = runBlocking {
val result = mySuspendFunction()
assertEquals("expected", result)
}
}
Per i test, si consiglia di utilizzare kotlinx-coroutines-test con TestCoroutineDispatcher invece di runBlocking — questo fornisce controllo del tempo ed evita blocchi nell'ambiente di test.
La scelta del Coroutine Builder dipende dal risultato restituito e dallo scenario di esecuzione. Se l'operazione non richiede la restituzione di dati — usa launch. Se hai bisogno del risultato di un'operazione asincrona — usa async. runBlocking dovrebbe essere usato solo come ponte e sostituisci produce con Flow per i flussi reattivi.
Nei progetti Android che utilizzano Kotlin Coroutines, la coppia principale di costruttori sono launch e async. launch viene utilizzato in ViewModel e UseCases per avviare coroutine, mentre async viene utilizzato per richieste parallele alla rete o al database. Le librerie moderne (Ktor, Room) supportano già funzioni suspend, il che minimizza la necessità dell'uso diretto di async.
Domande frequenti
launch restituisce Job e non restituisce un risultato di esecuzione, mentre async restituisce Deferred<T> — un oggetto da cui ottenere il risultato tramite await(). launch viene usato per operazioni fire-and-forget, async per attività che restituiscono dati.
Sconsigliato. runBlocking sul thread principale causa ANR e blocca l'interfaccia. Usa lifecycleScope.launch all'interno di Activity e Fragment — è una soluzione integrata senza blocchi.
Il costruttore launch restituisce un oggetto Job, che consente di controllare il ciclo di vita della coroutine: annullare (cancel), attendere il completamento (join), verificare lo stato (isActive, isCompleted, isCancelled).
Deferred<T> è una promessa asincrona di un risultato, restituita dal costruttore async. Eredita da Job e aggiunge metodi: await() per ottenere il risultato, getCompleted() per accesso non bloccante e getCompletionExceptionOrNull() per verificare le eccezioni.
Usa il parametro CoroutineStart.LAZY: scope.launch(start = CoroutineStart.LAZY) { ... }. Quindi chiama job.start() o job.join() per l'esecuzione effettiva. Ciò è utile per l'inizializzazione ritardata e l'esecuzione condizionale delle coroutine.
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