withContext — è una funzione di cambio di contesto di esecuzione all'interno di una coroutine che modifica temporaneamente il thread o dispatcher per un blocco di codice specificato e restituisce il risultato al contesto originale. Secondo JetBrains, 2025, withContext è uno degli strumenti di coroutine più utilizzati per richieste di rete e operazioni su disco. La funzione garantisce che dopo il completamento del blocco, la coroutine continui l'esecuzione sul dispatcher originale, prevenendo errori accidentali di thread safety.
Punti chiave
withContext è una funzione sospensiva del pacchetto kotlinx.coroutines che esegue il blocco di codice passato in un CoroutineContext specificato e restituisce il risultato al contesto originale. La firma della funzione è la seguente:
suspend fun withContext (
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
Il parametro context accetta qualsiasi CoroutineContext — più comunemente uno dei Dispatchers.IO, Dispatchers.Default o Dispatchers.Main standard. Il blocco viene eseguito in quel contesto e il risultato viene restituito al punto in cui withContext è stato chiamato.
Dopo il completamento del lambda, withContext riporta garantitamente l'esecuzione al dispatcher originale. Ciò significa che lo sviluppatore non deve chiamare manualmente withContext(Dispatchers.Main) dopo un'operazione in background — il ritorno avviene automaticamente. Questo comportamento è documentato nelle specifiche di Kotlin Coroutines dalla versione 1.3.
Lo sviluppo Android è l'area principale di utilizzo di withContext. Uno scenario tipico: un ViewModel avvia una coroutine sul thread principale, all'interno chiama withContext(Dispatchers.IO) per una richiesta di rete e il risultato dopo il ritorno automatico a Main viene utilizzato per aggiornare l'interfaccia utente. Questo approccio è alla base dell'architettura MVVM ed è raccomandato da Google nella guida ufficiale alle coroutine.
Per capire withContext, è necessario comprendere CoroutineContext e il suo componente chiave — il dispatcher. Ogni coroutine ha un insieme di elementi di contesto, tra i quali il dispatcher determina su quale thread o pool di thread viene eseguito il codice.
| Dispatcher | Scopo | Dimensione pool |
|---|---|---|
| Dispatchers.Main | Thread principale UI (Android, JavaFX, Swing) | 1 (thread principale) |
| Dispatchers.IO | Operazioni su disco e rete | 64 thread (limite cresce) |
| Dispatchers.Default | Calcoli intensivi di CPU | max(2, numero di core) |
| Dispatchers.Unconfined | Senza thread fisso | illimitato |
È importante capire che withContext non crea una nuova coroutine — cambia solo il contesto per quella esistente. Questa è una differenza chiave rispetto a launch e async, che generano nuove coroutine. L'implementazione interna di withContext è ottimizzata: se il contesto richiesto corrisponde a quello attuale, non si verifica alcun cambio — la funzione viene eseguita sullo stesso dispatcher.
Dispatchers.Main all'interno di withContext(Dispatchers.Main) non causa un cambio — Kotlin Coroutines riconosce l'identità dei contesti e salta l'operazione non necessaria. Allo stesso modo, withContext(Dispatchers.Default) all'interno di una coroutine già in esecuzione su Default non crea overhead. Questa ottimizzazione è implementata in ContinuationInterceptor.
I principianti spesso confondono withContext con launch e async, poiché tutte e tre le funzioni lavorano con coroutine e contesto. Tuttavia, il loro scopo è fondamentalmente diverso.
| Caratteristica | withContext | launch | async |
|---|---|---|---|
| Crea nuova coroutine | No | Sì | Sì |
| Restituisce risultato | Sì (T direttamente) | No (Job) | Sì (Deferred<T>) |
| Esecuzione | Sequenziale | Parallela | Parallela |
| Attesa del risultato | Automatica | join() | await() |
| Cas d'uso tipico | Cambio dispatcher | Fuoco-e-dimentica | Calcoli paralleli |
Se è necessario eseguire un'operazione su un thread in background e ottenere un risultato — usa withContext. Se è necessario eseguire più operazioni indipendenti in parallelo — usa async con await. Se non serve il risultato (logging, scrittura cache) — usa launch. Google raccomanda withContext come strumento preferito per il livello Repository nell'architettura Android.
Vediamo tre scenari pratici di utilizzo di withContext in applicazioni Android con Kotlin. Ogni esempio dimostra un compito specifico e il pattern corretto.
Un ViewModel chiama un metodo del repository da una coroutine su Main. All'interno, withContext(Dispatchers.IO) esegue una richiesta HTTP e il risultato viene restituito automaticamente:
class UserRepository(
private val api: UserApi
) {
suspend fun getUser(id: String): User {
return withContext(Dispatchers.IO) {
api.fetchUser(id)
}
}
}
La coroutine nel ViewModel chiama getUser come qualsiasi normale funzione sospensiva — senza specificare esplicitamente il dispatcher. withContext nasconde i dettagli del cambio di thread.
Quando è necessario eseguire più operazioni IO una dopo l'altra, withContext le combina in un unico blocco. È più efficiente che avvolgere ogni operazione in un withContext separato:
suspend fun loadUserProfile(id: String): Profile {
return withContext(Dispatchers.IO) {
val user = api.fetchUser(id)
val posts = api.fetchPosts(id)
Profile(user, posts)
}
}
Entrambe le operazioni vengono eseguite su Dispatchers.IO e il risultato Profile viene creato e restituito senza cambi di contesto inutili. Se le operazioni sono indipendenti, è meglio usare async per l'esecuzione parallela.
In alcuni scenari, è necessario eseguire codice che non può essere annullato — ad esempio, salvare lo stato durante la chiusura di una schermata. La combinazione di withContext + NonCancellable risolve questo compito:
withContext(Dispatchers.IO + NonCancellable) {
cache.saveState(state)
analytics.logEvent("state_saved")
}
L'operatore + combina due elementi di contesto: il dispatcher IO e il flag NonCancellable. Il blocco viene eseguito anche se la coroutine padre è stata annullata — utile per operazioni di finalizzazione.
L'implementazione interna di withContext si basa sul meccanismo Continuation — l'astrazione centrale delle coroutine Kotlin. Ogni punto di sospensione salva lo stato di esecuzione in un oggetto Continuation, e withContext non fa eccezione.
Il compilatore Kotlin traduce withContext in una chiamata al metodo withContext di kotlinx.coroutines, che internamente crea una nuova istanza di DispatchedContinuation. Questo oggetto avvolge la Continuation originale e sostituisce il suo dispatcher. Se il nuovo dispatcher differisce da quello corrente, l'esecuzione viene sospesa, il blocco viene inviato al pool di thread corrispondente e, dopo il completamento — riprende con il contesto originale.
Quando withContext viene chiamato con lo stesso dispatcher su cui la coroutine è già in esecuzione, Kotlin attiva il fast-path: il blocco viene eseguito in modo sincrono, senza creare un DispatchedContinuation e senza inviarlo al pool di thread. Ciò rende withContext praticamente gratuito per chiamate ripetute con lo stesso contesto. Secondo i benchmark di JetBrains (kotlinx.coroutines 1.8), il fast-path viene completato in meno di 0,1 µs.
Ogni chiamata a withContext con un dispatcher diverso crea un nuovo DispatchedContinuation e richiede un cambio di thread — ci vogliono da 1 a 5 µs a seconda del carico. Per la maggior parte delle applicazioni, questo ritardo è impercettibile, ma all'interno di cicli con migliaia di iterazioni, vale la pena aggregare le operazioni in un unico blocco withContext.
Anche gli sviluppatori esperti commettono errori lavorando con withContext. Vediamo quattro problemi comuni e come prevenirli.
Gli sviluppatori spesso avvolgono ogni riga in un withContext separato invece di combinare le operazioni in un unico blocco. Ogni chiamata extra con un dispatcher diverso crea overhead.
Corretto: combinare operazioni IO sequenziali in un unico withContext(Dispatchers.IO) { ... }. Se alcune operazioni sono intensive di CPU — usa withContext(Dispatchers.Default) all'interno dello stesso blocco.
withContext esegue il codice in modo sequenziale. Se due richieste di rete indipendenti sono avvolte in un unico withContext, verranno eseguite una dopo l'altra. Per il parallelismo, usa async + await.
// Sequenziale — lento
withContext(Dispatchers.IO) {
val a = api.fetchA()
val b = api.fetchB()
}
// Parallelo — veloce
coroutineScope {
val a = async { api.fetchA() }
val b = async { api.fetchB() }
println("${a.await()} ${b.await()}")
}
Se una coroutine viene annullata durante withContext, il blocco su Dispatchers.IO viene anch'esso interrotto. Per le operazioni che devono essere completate a tutti i costi (scrittura database, invio analisi), combina withContext con NonCancellable.
Non aggiornare mai i componenti View all'interno di withContext(Dispatchers.IO). withContext non torna a Main fino al completamento dell'intero blocco. Posiziona gli aggiornamenti UI dopo la parentesi di chiusura di withContext — allora la coroutine sarà già sul thread principale.
Domande frequenti
withContext è una funzione sospensiva che non blocca il thread, ma cambia il contesto all'interno di una coroutine esistente. runBlocking è un ponte tra coroutine e codice normale che blocca il thread corrente fino al completamento. withContext è sicuro per il thread UI, runBlocking no.
No, withContext è una funzione suspend, quindi può essere chiamata solo da un'altra funzione suspend o da una coroutine (launch/async). Da una funzione normale, withContext non può essere chiamato — per questo serve runBlocking o CoroutineScope.
Kotlin attiva il fast-path — il blocco viene eseguito in modo sincrono sullo stesso thread senza cambio. L'overhead è inferiore a 0,1 µs. Questo non è un errore, ma tale chiamata è ridondante — è meglio eseguire il codice semplicemente senza withContext.
Le eccezioni all'interno di withContext si propagano come nel codice normale — tramite try-catch. Se il blocco lancia un'eccezione, si propaga alla coroutine padre e la annulla se non viene gestita. Usa try-catch dentro withContext o intorno ad esso.
No, withContext non crea una nuova coroutine. Usa la coroutine esistente ma cambia temporaneamente il suo contesto. Questo lo distingue da launch e async, che generano coroutine figlie. Questo comportamento è confermato dal codice sorgente di kotlinx.coroutines.
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