CoroutineScope — είναι μια διεπαφή της Kotlin που καθορίζει την περιοχή ζωής μιας συνεταιριστικής διαδικασίας και παρέχει πλαίσιο για την εκκίνηση νέων κορουτινών. Σύμφωνα με την τεκμηρίωση της Kotlin, 2025, κάθε στιγμιότυπο CoroutineScope περιέχει ένα CoroutineContext και διαχειρίζεται όλες τις κορουτίνες που εκτελούνται σε αυτό. Όταν το scope τερματίζεται (cancel), όλες οι θυγατρικές κορουτίνες ακυρώνονται αυτόματα, αποτρέποντας διαρροές μνήμης.
Βασικά σημεία
CoroutineScope — είναι μια θεμελιώδης διεπαφή από τη βιβλιοθήκη kotlinx.coroutines, η οποία λειτουργεί ως δοχείο για κορουτίνες. Καθορίζει τα όρια ζωής των συνεταιριστικών διαδικασιών: όταν το scope τερματίζεται, όλες οι κορουτίνες εντός αυτού ακυρώνονται αυτόματα.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
Η διεπαφή περιέχει μόνο ένα πεδίο — coroutineContext. Μέσω αυτού, το scope παρέχει τον διακομιστή (Dispatcher), την εργασία (Job), τον χειριστή εξαιρέσεων και άλλα στοιχεία του πλαισίου για όλες τις κορουτίνες που εκτελούνται σε αυτό.
Όλες οι συναρτήσεις εκκίνησης κορουτινών — launch, async, runBlocking — είναι συναρτήσεις επέκτασης (extension functions) στο CoroutineScope. Αυτό σημαίνει ότι μπορούν να κληθούν μόνο με την παρουσία ενός αντικειμένου scope. Αυτός ο σχεδιασμός διασφαλίζει ότι κάθε κορουτίνα έχει έναν σαφώς καθορισμένο γονέα και κύκλο ζωής.
Στο Android, κάθε αρχιτεκτονικό στοιχείο έχει το δικό του scope: viewModelScope για ViewModel, lifecycleScope για Activity/Fragment. Σε εφαρμογές διακομιστή, το scope μπορεί να συνδεθεί με ένα HTTP αίτημα ή με μια ομάδα συνδέσεων βάσης δεδομένων.
Η κατανόηση της εσωτερικής λειτουργίας του CoroutineScope απαιτεί εξοικείωση με την έννοια του Job και την αρχή του δομικού ανταγωνισμού.
Κάθε κορουτίνα κατά την εκκίνηση επιστρέφει ένα αντικείμενο Job (ή Deferred για async). Το Job αντιπροσωπεύει μια εργασία με πεπερασμένο κύκλο ζωής: New, Active, Completing, Completed, Cancelling, Cancelled. Τα αντικείμενα Job σχηματίζουν μια δενδρική δομή:
Δομικός ανταγωνισμός — βασική αρχιτεκτονική αρχή των Kotlin Coroutines, κατά την οποία η διάρκεια ζωής μιας κορουτίνας συνδέεται με τη διάρκεια ζωής του scope της. Αυτό έρχεται σε αντίθεση με το μοντέλο «fire-and-forget», όπου η κορουτίνα συνεχίζει να ζει μετά τον τερματισμό του scope. Πλεονεκτήματα του δομικού ανταγωνισμού:
Όταν καλείται η scope.cancel(), το Job του scope μεταβαίνει σε κατάσταση Cancelled, η οποία ακυρώνει αναδρομικά όλα τα θυγατρικά Job. Μετά την ακύρωση, το scope μπορεί να επαναχρησιμοποιηθεί μόνο με τη δημιουργία ενός νέου στιγμιότυπου CoroutineScope.
Μπορείτε να δημιουργήσετε ένα CoroutineScope μέσω της συνάρτησης εργοστασίου ή μέσω υλοποίησης της διεπαφής στην κλάση σας. Ας εξετάσουμε και τις δύο προσεγγίσεις.
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Εκτελείται σε ${Thread.currentThread().name}")
}
Η συνάρτηση εργοστασίου δέχεται ένα CoroutineContext και δημιουργεί ένα scope με το καθορισμένο πλαίσιο. Στο παράδειγμα χρησιμοποιείται Dispatchers.Default για εργασίες έντασης CPU και SupervisorJob, το οποίο απομονώνει τις εξαιρέσεις μεταξύ των θυγατρικών κορουτινών.
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
Αποθηκεύουμε το scope ως πεδίο της κλάσης και καλούμε χειροκίνητα τη cleanup για την ακύρωσή του. Αυτό είναι κατάλληλο για στοιχεία με διαχειρίσιμο κύκλο ζωής — για παράδειγμα, για αποθετήρια ή διαχειριστές.
Η Kotlin επιτρέπει την ανάθεση της υλοποίησης του CoroutineScope μέσω της λέξης-κλειδιού by:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// η κορουτίνα εκτελείται στο πεδίο DataLoader
}
}
}
Αυτή η προσέγγιση είναι βολική όταν η κλάση είναι η ίδια scope και θέλει να παρέχει μεθόδους εκκίνησης κορουτινών. Ωστόσο, να είστε προσεκτικοί: η κλάση κληρονομεί όλες τις μεθόδους του CoroutineScope, συμπεριλαμβανομένης της cancel, η οποία μπορεί να παραβιάσει την ενσωμάτωση.
GlobalScope — είναι ένα στιγμιότυπο singleton CoroutineScope για ολόκληρη την εφαρμογή. Η χρήση του σε production κώδικα δεν συνιστάται επίσημα.
Η JetBrains επιτρέπει το GlobalScope μόνο σε σπάνια σενάρια: διεργασίες παρασκηνίου σε επίπεδο εφαρμογής που πρέπει να ζουν ακόμη και μετά το κλείσιμο όλων των Activity (για παράδειγμα, συγχρονισμός δεδομένων, αναλυτικά στοιχεία). Αλλά και σε αυτές τις περιπτώσεις, προτιμάται η δημιουργία ενός δικού σας scope με CoroutineScope(SupervisorJob()).
Χρησιμοποιείτε πάντα custom CoroutineScope με ρητή διαχείριση κύκλου ζωής. Στο Android, αυτά είναι τα viewModelScope και lifecycleScope. Σε εφαρμογές διακομιστή, δημιουργήστε scope για κάθε αίτημα ή ομάδα συνδέσεων.
Και οι δύο συναρτήσεις είναι suspend-συναρτήσεις που δημιουργούν ένα προσωρινό scope για παράλληλες εργασίες, αλλά η συμπεριφορά τους σε εξαιρέσεις διαφέρει ριζικά.
| Χαρακτηριστικό | coroutineScope | supervisorScope |
|---|---|---|
| Συμπεριφορά σε σφάλμα | Εξαίρεση σε θυγατρική κορουτίνα ακυρώνει όλες τις υπόλοιπες | Εξαίρεση σε θυγατρική κορουτίνα ΔΕΝ ακυρώνει τις υπόλοιπες |
| Διάδοση σφάλματος | Ναι, η πρώτη εξαίρεση διαδίδεται προς τα έξω | Ναι, η πρώτη εξαίρεση διαδίδεται προς τα έξω |
| Προεπιλεγμένο Job | Job() — θυγατρικές συνδεδεμένες με γονέα | SupervisorJob() — θυγατρικές ανεξάρτητες μεταξύ τους |
| Τυπική περίπτωση χρήσης | Ατομική λειτουργία πολλών βημάτων | Ανεξάρτητες παράλληλες εργασίες (UI φορτώσεις) |
Χρησιμοποιήστε coroutineScope όταν πολλές παράλληλες λειτουργίες αποτελούν μια ενιαία ατομική λειτουργία. Για παράδειγμα, φόρτωση δεδομένων από τρεις διακομιστές: αν ένα αίτημα αποτύχει, τα υπόλοιπα δεν έχουν νόημα.
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
Αν η getProduct ή η getReviews ρίξουν εξαίρεση — και οι δύο κορουτίνες ακυρώνονται και η εξαίρεση διαδίδεται στον καλούντα κώδικα.
Χρησιμοποιήστε supervisorScope όταν οι παράλληλες λειτουργίες είναι ανεξάρτητες μεταξύ τους. Για παράδειγμα, φόρτωση δεδομένων προφίλ σε πολλές ανεξάρτητες ενότητες: αν η ενότητα συστάσεων αποτύχει, η κεφαλίδα προφίλ και η λίστα φίλων πρέπει να εμφανιστούν.
Ας εξετάσουμε τα πιο συχνά λάθη προγραμματιστών κατά τη χρήση του CoroutineScope στην Kotlin.
Το πιο συνηθισμένο σενάριο διαρροής κορουτίνας — δημιουργία scope χωρίς κλήση cancel κατά τον τερματισμό του στοιχείου. Αν το scope δεν ακυρωθεί, οι κορουτίνες συνεχίζουν να λειτουργούν, κρατώντας αναφορές σε αντικείμενα. Στο Android, χρησιμοποιήστε viewModelScope ή lifecycleScope, τα οποία ακυρώνονται αυτόματα.
Το GlobalScope αγνοεί τον κύκλο ζωής των Android στοιχείων. Μια κορουτίνα που εκκινήθηκε στο GlobalScope μετά το κλείσιμο του Activity θα συνεχίσει να εκτελείται και θα προσπαθήσει να ενημερώσει το UI — προκαλώντας σφάλμα. Χρησιμοποιείτε πάντα lifecycleScope για UI στοιχεία.
Μετά την κλήση cancel() το scope δεν μπορεί να επαναχρησιμοποιηθεί — όλες οι κορουτίνες σε αυτό έχουν ήδη τερματιστεί. Δημιουργήστε ένα νέο στιγμιότυπο CoroutineScope μέσω της συνάρτησης εργοστασίου. Η Job() δεν υποστηρίζει επανενεργοποίηση.
Κατά την ανάθεση με by, η κλάση αποκτά τη δημόσια μέθοδο cancel(), η οποία μπορεί να κληθεί από οπουδήποτε, παραβιάζοντας την ενσωμάτωση. Αποθηκεύστε το scope ως ιδιωτικό πεδίο, αντί να αναθέτετε τη διεπαφή.
Συχνές ερωτήσεις
CoroutineScope — είναι μια διεπαφή που κατέχει ένα CoroutineContext και είναι υπεύθυνη για τον κύκλο ζωής των κορουτινών. CoroutineContext — είναι ένα σύνολο στοιχείων (διακομιστής, job, χειριστής σφαλμάτων) που καθορίζει «πώς» εκτελείται η κορουτίνα. Μία από τις διαφορές: το scope δημιουργεί κορουτίνες, το πλαίσιο διαχειρίζεται τη συμπεριφορά τους.
Ναι, αυτό είναι ένα τυπικό μοτίβο: CoroutineScope(Dispatchers.IO + SupervisorJob()). Το SupervisorJob αποτρέπει την αλυσιδωτή ακύρωση θυγατρικών κορουτινών όταν υπάρχει εξαίρεση σε μία από αυτές. Είναι χρήσιμο για ανεξάρτητες παράλληλες εργασίες όπου ένα σφάλμα σε μία δεν πρέπει να σταματήσει τις άλλες.
Δεν υπάρχει περιορισμός στον αριθμό κορουτινών σε ένα scope — περιορίζονται μόνο από τη διαθέσιμη μνήμη και τις ρυθμίσεις του διακομιστή. Το πρακτικό όριο είναι συνήθως χιλιάδες ενεργές κορουτίνες σε ένα scope. Ωστόσο, ένας μεγάλος αριθμός κορουτινών μπορεί να υποδηλώνει αρχιτεκτονικά προβλήματα.
Ο σωστός τρόπος είναι να μεταβιβάζετε το scope στην κλάση μέσω κατασκευαστή ή να χρησιμοποιείτε runBlockingTest / runTest από το kotlinx-coroutines-test. Στις δοκιμές, μπορείτε να αντικαταστήσετε το scope με TestCoroutineDispatcher και να ελέγχετε την εκτέλεση των κορουτινών χειροκίνητα.
Όχι, το scope είναι ένα εξωτερικό δοχείο για την κορουτίνα. Η ίδια η κορουτίνα δεν είναι scope. Ωστόσο, μέσα σε μια κορουτίνα μπορείτε να δημιουργήσετε ένα νέο scope μέσω coroutineScope ή supervisorScope για παράλληλη εκκίνηση θυγατρικών κορουτινών.
Συμπεράσματα
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης