Suspend function è una funzione con il modificatore suspend che può sospendere la propria esecuzione senza bloccare un thread e riprendere più tardi nella stessa coroutine. Secondo JetBrains Kotlin Docs, 2025, le funzioni suspend sono un blocco fondamentale delle coroutine, fornendo asincronicità senza callback. Ogni funzione suspend viene compilata in una macchina a stati basata su Continuation, consentendo una gestione efficiente dei punti di sospensione.
Punti chiave
Suspend function è una funzione dichiarata con la parola chiave suspend che può sospendere l'esecuzione in uno o più punti senza bloccare il thread. Ogni chiamata a una funzione suspend all'interno di un'altra funzione suspend è un potenziale punto di sospensione.
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
Il compilatore Kotlin traduce tale funzione in una macchina a stati. Ogni punto di sospensione (una chiamata a un'altra funzione suspend) diventa uno stato (label). Il thread corrente viene rilasciato tra gli stati e, una completata l'operazione attesa, l'esecuzione riprende dallo stato successivo.
Le funzioni suspend sono apparse in Kotlin 1.3 (2018) insieme alle coroutine come funzionalità sperimentale e sono diventate stabili in Kotlin 1.5 (2021). Prima di allora, l'asincronicità in Kotlin/Java era ottenuta tramite callback, RxJava e CompletableFuture. Le funzioni suspend hanno offerto un'alternativa con sintassi lineare e gestione automatica dei thread.
Comprendere il funzionamento interno delle funzioni suspend è la chiave per lavorare correttamente con le coroutine. A differenza delle funzioni normali, ogni funzione suspend viene compilata in una classe con l'interfaccia Continuation.
Il compilatore Kotlin aggiunge un parametro di tipo Continuation alla fine di ogni elenco di parametri di una funzione suspend. Continuation contiene:
Supponiamo di avere una funzione suspend con due chiamate ad altre funzioni suspend:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
Il compilatore la trasforma in una macchina a stati con etichette:
// Simplified generated code representation
fun process(cont: Continuation<Unit>): Any? {
val cont = cont as ProcessContinuation
when (cont.label) {
0 -> {
cont.label = 1
if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
1 -> {
cont.label = 2
val a = cont.result as TypeA
if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
2 -> {
println(cont.result)
Unit
}
}
}
Osservazione chiave: se la funzione restituisce COROUTINE_SUSPENDED, il thread corrente viene rilasciato. Quando l'operazione asincrona si completa, viene chiamato Continuation.resumeWith e la macchina a stati continua dall'etichetta successiva.
Dichiarare una funzione suspend non è diverso da una funzione normale, eccetto per la parola chiave suspend prima di fun. C'è solo una restrizione: una funzione suspend può essere chiamata solo da una coroutine o da un'altra funzione suspend.
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
In questo esempio, delay è anch'essa una funzione suspend che sospende la coroutine per il numero specificato di millisecondi senza bloccare il thread. Dopo il ritardo, l'esecuzione riprende.
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlocking crea un ponte tra il mondo normale e le coroutine. All'interno del lambda, qualsiasi funzione suspend può essere chiamata.
Kotlin supporta versioni suspend dei tipi funzionali — suspend () -> T e suspend (A) -> B. Ciò consente di passare lambda asincrone a funzioni di ordine superiore:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
La funzione withRetry accetta un lambda suspend e ne riprova l'esecuzione in caso di errore. Questo è un modello tipico per richieste di rete con tentativi.
Le differenze tra funzioni suspend e normali vanno oltre la semplice aggiunta di un modificatore. Esaminiamo le distinzioni principali.
| Caratteristica | Funzione normale | Funzione suspend |
|---|---|---|
| Thread di esecuzione | Blocca il thread fino al completamento | Può rilasciare il thread e riprendere dopo |
| Parametri del compilatore | Solo i parametri specificati | Continuation implicito alla fine |
| Chiamata da funzione normale | Sì | No |
| Stack | Stack fisico del thread | Macchina a stati nell'heap + stack fisico tra i punti |
| Valore restituito | Valore diretto | Valore o COROUTINE_SUSPENDED |
| Prestazioni | Overhead minimo | ~pochi nanosecondi per macchina a stati (Kotlin 1.9+) |
Una funzione normale non ha un Continuation — non ha un posto dove salvare lo stato e nulla con cui riprendere l'esecuzione. Se devi chiamare una funzione suspend da una funzione normale, usa runBlocking (per i test) o CoroutineScope.launch (per la produzione con consapevolezza del ciclo di vita).
Esaminiamo tre scenari reali di utilizzo delle funzioni suspend nelle app Android con Kotlin.
Room supporta le funzioni suspend direttamente — la query viene eseguita automaticamente su un thread in background:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Room utilizza internamente Dispatchers.IO per eseguire la query e il risultato viene restituito sul dispatcher in cui è stata chiamata la funzione suspend.
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launch crea una coroutine, all'interno della quale viene chiamata la funzione suspend getProfile. Dopo aver ottenuto il risultato, l'interfaccia utente viene aggiornata sul thread principale.
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
Tre funzioni suspend vengono eseguite sequenzialmente. A ogni passaggio, la coroutine può sospendersi senza bloccare il thread. Se un passaggio genera un'eccezione, i passaggi rimanenti non vengono eseguiti, proteggendo da stati di ordine errati.
Anche gli sviluppatori Kotlin esperti commettono errori nella progettazione di funzioni suspend. Esaminiamo i più comuni.
Una funzione suspend non rende il codice automaticamente asincrono. Thread.sleep(), InputStream.read() e altre chiamate bloccanti continueranno a bloccare il thread. Usa withContext(Dispatchers.IO) per incapsulare le operazioni bloccanti.
Se una funzione non chiama altre funzioni suspend e non esegue operazioni asincrone — il modificatore suspend è ridondante. Aggiunge overhead alla macchina a stati e limita il contesto di chiamata. Rendi una funzione suspend solo quando effettivamente si sospende.
Quando una coroutine viene annullata, le funzioni suspend lanciano CancellationException. Non catturarla sconsideratamente — stai privando il codice chiamante della capacità di completare correttamente l'annullamento. Se devi eseguire un'operazione di finalizzazione, usa un blocco finally e NonCancellable.
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
Il blocco finally viene sempre eseguito, incluso in caso di annullamento, e NonCancellable garantisce che la pulizia non venga interrotta.
Non puoi chiamare una funzione suspend direttamente da un callback senza creare una coroutine. Usa suspendCoroutine o suspendCancellableCoroutine per adattare lo stile callback alle coroutine.
Domande frequenti
Sì, tecnicamente una funzione suspend può non chiamare altre funzioni suspend. Il compilatore creerà una macchina a stati con un unico stato (label 0). Tuttavia, non c'è alcun beneficio pratico in tale funzione — viene eseguita come una normale ma con overhead. Non usare suspend inutilmente.
Kotlin fornisce kotlinx-coroutines-debug — una libreria con DebugProbes e strumenti di tracciamento delle coroutine. In Android Studio a partire da Arctic Fox, c'è una scheda Coroutines integrata nel Debugger che mostra le coroutine attive, il loro stato e i punti di sospensione.
Ogni punto di sospensione crea un nuovo stato nella macchina a stati. Per la maggior parte delle applicazioni, l'overhead di un punto è di pochi nanosecondi (Kotlin 1.9+). Solo con decine di migliaia di punti in un ciclo dovresti considerare di combinare operazioni o usare sequence/flow.
In Kotlin, suspend è un modificatore di tipo di funzione, non un marcatore di valore restituito (come async in C#). Qualsiasi funzione suspend può avere qualsiasi parametro e tipo restituito, e la sua chiamata sintatticamente non differisce da una chiamata di funzione normale — non c'è un operatore await nel punto di chiamata.
Usa suspendCancellableCoroutine per l'adattamento. All'interno, registri un callback che chiama continuation.resume() e restituisci un token di annullamento se il callback supporta la disiscrizione. Questo è il modello standard per incapsulare vecchie API Android.
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