Suspend function — είναι μια συνάρτηση με τον τροποποιητή suspend που μπορεί να αναστείλει την εκτέλεσή της χωρίς να μπλοκάρει το νήμα και να συνεχίσει αργότερα στο ίδιο coroutine. Σύμφωνα με το JetBrains Kotlin Docs, 2025, οι συναρτήσεις suspend είναι το θεμελιώδες δομικό στοιχείο των coroutines, παρέχοντας ασυγχρονισμό χωρίς callback. Κάθε συνάρτηση suspend μεταγλωττίζεται σε μια μηχανή καταστάσεων βασισμένη στο Continuation, επιτρέποντας την αποτελεσματική διαχείριση των σημείων αναστολής.
Κύρια σημεία
Suspend function — είναι μια συνάρτηση δηλωμένη με τη λέξη-κλειδί suspend που μπορεί να αναστείλει την εκτέλεση σε ένα ή περισσότερα σημεία χωρίς να μπλοκάρει το νήμα. Κάθε κλήση μιας συνάρτησης suspend μέσα σε άλλη συνάρτηση suspend είναι ένα δυνητικό σημείο αναστολής.
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
Ο μεταγλωττιστής Kotlin μετατρέπει μια τέτοια συνάρτηση σε μηχανή καταστάσεων. Κάθε σημείο αναστολής (κλήση άλλης συνάρτησης suspend) γίνεται μια κατάσταση (label). Το τρέχον νήμα απελευθερώνεται μεταξύ των καταστάσεων και μετά την ολοκλήρωση της αναμενόμενης λειτουργίας, η εκτέλεση συνεχίζεται από την επόμενη κατάσταση.
Οι συναρτήσεις suspend εμφανίστηκαν στο Kotlin 1.3 (2018) μαζί με τα coroutines ως πειραματική λειτουργία και έγιναν σταθερές στο Kotlin 1.5 (2021). Πριν από αυτό, ο ασυγχρονισμός στο Kotlin/Java παρεχόταν μέσω callback, RxJava και CompletableFuture. Οι συναρτήσεις suspend πρόσφεραν μια εναλλακτική με γραμμική σύνταξη και αυτόματη διαχείριση νημάτων.
Η κατανόηση της εσωτερικής δομής των συναρτήσεων suspend είναι το κλειδί για τη σωστή εργασία με coroutines. Σε αντίθεση με τις συνηθισμένες συναρτήσεις, κάθε συνάρτηση suspend μεταγλωττίζεται σε μια κλάση με διεπαφή Continuation.
Ο μεταγλωττιστής Kotlin προσθέτει μια παράμετρο τύπου Continuation στο τέλος κάθε παραμέτρου της συνάρτησης suspend. Το Continuation περιέχει:
Ας υποθέσουμε ότι έχουμε μια συνάρτηση suspend με δύο κλήσεις σε άλλες συναρτήσεις suspend:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
Ο μεταγλωττιστής τη μετατρέπει σε μηχανή καταστάσεων με ετικέτες:
// Απλοποιημένη αναπαράσταση παραγόμενου κώδικα
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
}
}
}
Κρίσιμη παρατήρηση: αν η συνάρτηση επιστρέψει COROUTINE_SUSPENDED, το τρέχον νήμα απελευθερώνεται. Όταν η ασύγχρονη λειτουργία ολοκληρωθεί, καλείται το Continuation.resumeWith και η μηχανή καταστάσεων συνεχίζει από την επόμενη ετικέτα.
Η δήλωση μιας συνάρτησης suspend δεν διαφέρει από μια συνηθισμένη, εκτός από τη λέξη-κλειδί suspend πριν από το fun. Υπάρχει μόνο ένας περιορισμός: μια συνάρτηση suspend μπορεί να κληθεί μόνο από coroutine ή άλλη συνάρτηση suspend.
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
Σε αυτό το παράδειγμα, η delay είναι επίσης μια συνάρτηση suspend που αναστέλλει το coroutine για τον καθορισμένο αριθμό χιλιοστών του δευτερολέπτου χωρίς να μπλοκάρει το νήμα. Μετά την καθυστέρηση, η εκτέλεση συνεχίζεται.
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
Το runBlocking δημιουργεί μια γέφυρα μεταξύ του συνηθισμένου κόσμου και των coroutines. Μέσα στο lambda, επιτρέπεται η κλήση οποιασδήποτε συνάρτησης suspend.
Το Kotlin υποστηρίζει εκδόσεις suspend των συναρτησιακών τύπων — suspend () -> T και suspend (A) -> B. Αυτό επιτρέπει τη μετάδοση ασύγχρονων lambda σε συναρτήσεις ανώτερης τάξης:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
Η συνάρτηση withRetry δέχεται ένα suspend lambda και επαναλαμβάνει την εκτέλεσή του σε περίπτωση σφαλμάτων. Αυτό είναι ένα τυπικό μοτίβο για αιτήματα δικτύου με επαναλήψεις.
Οι διαφορές μεταξύ των συναρτήσεων suspend και των συνηθισμένων υπερβαίνουν την απλή προσθήκη ενός τροποποιητή. Ας εξετάσουμε τις θεμελιώδεις διαφορές.
| Χαρακτηριστικό | Συνηθισμένη συνάρτηση | Suspend συνάρτηση |
|---|---|---|
| Νήμα εκτέλεσης | Μπλοκάρει το νήμα μέχρι την ολοκλήρωση | Μπορεί να απελευθερώσει το νήμα και να συνεχίσει αργότερα |
| Παράμετροι μεταγλωττιστή | Μόνο οι καθορισμένες παράμετροι | Σιωπηρό Continuation στο τέλος |
| Κλήση από συνηθισμένη συνάρτηση | Ναι | Όχι |
| Στοίβα | Φυσική στοίβα νήματος | Μηχανή καταστάσεων στο heap + φυσική στοίβα μεταξύ σημείων |
| Επιστροφή | Άμεση τιμή | Τιμή ή COROUTINE_SUSPENDED |
| Απόδοση | Ελάχιστο επιπλέον κόστος | ~μερικά νανοδευτερόλεπτα για μηχανή καταστάσεων (Kotlin 1.9+) |
Μια συνηθισμένη συνάρτηση δεν έχει Continuation — δεν έχει πού να αποθηκεύσει την κατάσταση και με τι να συνεχίσει την εκτέλεση. Αν χρειάζεται να καλέσετε μια συνάρτηση suspend από μια συνηθισμένη, χρησιμοποιήστε το runBlocking (για δοκιμές) ή το CoroutineScope.launch (για παραγωγή λαμβάνοντας υπόψη τον κύκλο ζωής).
Ας εξετάσουμε τρία πραγματικά σενάρια χρήσης συναρτήσεων suspend σε εφαρμογές Android με Kotlin.
Το Room υποστηρίζει απευθείας τις συναρτήσεις suspend — το ερώτημα εκτελείται αυτόματα στο παρασκήνιο:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Το Room χρησιμοποιεί εσωτερικά το Dispatchers.IO για την εκτέλεση του ερωτήματος και το αποτέλεσμα επιστρέφεται στον διανομέα όπου κλήθηκε η συνάρτηση suspend.
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
Το ViewModelScope.launch δημιουργεί ένα coroutine μέσα στο οποίο καλείται η συνάρτηση suspend getProfile. Μετά τη λήψη του αποτελέσματος, το UI ενημερώνεται στο κύριο νήμα.
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
Τρεις συναρτήσεις suspend εκτελούνται διαδοχικά. Σε κάθε βήμα, το coroutine μπορεί να σταματήσει χωρίς να μπλοκάρει το νήμα. Αν κάποιο βήμα ρίξει εξαίρεση — τα υπόλοιπα δεν εκτελούνται, προστατεύοντας από εσφαλμένες καταστάσεις παραγγελίας.
Ακόμα και έμπειροι προγραμματιστές Kotlin κάνουν λάθη κατά τον σχεδιασμό συναρτήσεων suspend. Ας δούμε τα πιο συνηθισμένα.
Η συνάρτηση suspend δεν καθιστά τον κώδικα αυτόματα ασύγχρονο. Τα Thread.sleep(), InputStream.read() και άλλες μπλοκάρισμα κλήσεις θα μπλοκάρουν και πάλι το νήμα. Χρησιμοποιήστε το withContext(Dispatchers.IO) για να τυλίξετε τις μπλοκάρισμα λειτουργίες.
Αν μια συνάρτηση δεν καλεί άλλες συναρτήσεις suspend και δεν εκτελεί ασύγχρονες λειτουργίες — ο τροποποιητής suspend είναι περιττός. Προσθέτει επιπλέον κόστος στη μηχανή καταστάσεων και περιορίζει το περιβάλλον κλήσης. Κάντε μια συνάρτηση suspend μόνο όταν πραγματικά αναστέλλεται.
Κατά την ακύρωση του coroutine, οι συναρτήσεις suspend ρίχνουν CancellationException. Μην το πιάνετε αλόγιστα — στερείτε από τον καλούντα κώδικα τη δυνατότητα να ολοκληρώσει σωστά την ακύρωση. Αν χρειάζεται να εκτελέσετε μια λειτουργία ολοκλήρωσης, χρησιμοποιήστε το μπλοκ finally και το NonCancellable.
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
Το μπλοκ finally εκτελείται πάντα, συμπεριλαμβανομένης της ακύρωσης, και το NonCancellable εγγυάται ότι ο καθαρισμός δεν θα διακοπεί.
Δεν μπορείτε να καλέσετε μια συνάρτηση suspend απευθείας από ένα callback χωρίς να δημιουργήσετε coroutine. Χρησιμοποιήστε το suspendCoroutine ή το suspendCancellableCoroutine για να προσαρμόσετε το στυλ callback στα coroutines.
Συχνές Ερωτήσεις
Ναι, τεχνικά μια συνάρτηση suspend μπορεί να μην καλεί άλλες συναρτήσεις suspend. Ο μεταγλωττιστής θα δημιουργήσει μια μηχανή καταστάσεων με μία κατάσταση (label 0). Ωστόσο, μια τέτοια συνάρτηση δεν έχει πρακτική χρησιμότητα — εκτελείται σαν συνηθισμένη, αλλά με επιπλέον κόστος. Μην χρησιμοποιείτε το suspend χωρίς ανάγκη.
Το Kotlin παρέχει τη βιβλιοθήκη kotlinx-coroutines-debug — με DebugProbes και εργαλεία ανίχνευσης coroutines. Στο Android Studio από την έκδοση Arctic Fox, υπάρχει μια ενσωματωμένη καρτέλα Coroutines στο Debugger που εμφανίζει τα ενεργά coroutines, την κατάσταση και τα σημεία αναστολής τους.
Κάθε σημείο αναστολής δημιουργεί μια νέα κατάσταση στη μηχανή καταστάσεων. Για τις περισσότερες εφαρμογές, το επιπλέον κόστος ενός σημείου είναι μονάδες νανοδευτερολέπτων (Kotlin 1.9+). Μόνο όταν υπάρχουν δεκάδες χιλιάδες σημεία σε έναν βρόχο, αξίζει να συνδυάσετε λειτουργίες ή να χρησιμοποιήσετε sequence/flow.
Στο Kotlin, το suspend είναι τροποποιητής του τύπου συνάρτησης, όχι δείκτης της επιστρεφόμενης τιμής (όπως το async στη C#). Οποιαδήποτε συνάρτηση suspend μπορεί να έχει οποιεσδήποτε παραμέτρους και τύπο επιστροφής, και η κλήση της συντακτικά δεν διαφέρει από την κλήση μιας συνηθισμένης συνάρτησης — δεν υπάρχει τελεστής await στο σημείο κλήσης.
Χρησιμοποιήστε το suspendCancellableCoroutine για προσαρμογή. Μέσα σε αυτό, περνάτε την εγγραφή callback που καλεί το continuation.resume() και επιστρέφετε ένα διακριτικό ακύρωσης αν το callback υποστηρίζει διαγραφή συνδρομής. Αυτό είναι το τυπικό μοτίβο για την ενσωμάτωση παλαιών Android API.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης