CoroutineScope — τι είναι, περιοχή ζωής και λειτουργία στις κορουτίνες

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-06-22 Χρόνος ανάγνωσης: 10 λεπ

CoroutineScope — είναι μια διεπαφή της Kotlin που καθορίζει την περιοχή ζωής μιας συνεταιριστικής διαδικασίας και παρέχει πλαίσιο για την εκκίνηση νέων κορουτινών. Σύμφωνα με την τεκμηρίωση της Kotlin, 2025, κάθε στιγμιότυπο CoroutineScope περιέχει ένα CoroutineContext και διαχειρίζεται όλες τις κορουτίνες που εκτελούνται σε αυτό. Όταν το scope τερματίζεται (cancel), όλες οι θυγατρικές κορουτίνες ακυρώνονται αυτόματα, αποτρέποντας διαρροές μνήμης.

Βασικά σημεία

  • CoroutineScope — διεπαφή με μοναδικό πεδίο CoroutineContext, που καθορίζει τον κύκλο ζωής των κορουτινών
  • Job — στοιχείο του πλαισίου που είναι υπεύθυνο για την ακύρωση: η ακύρωση του scope ακυρώνει όλες τις θυγατρικές κορουτίνες
  • Δομικός ανταγωνισμός — αρχή κατά την οποία οι θυγατρικές κορουτίνες συνδέονται με το γονικό scope
  • GlobalScope — scope για ολόκληρη την εφαρμογή, το οποίο δεν συνιστάται λόγω κινδύνου διαρροών
  • supervisorScope — ειδικό scope στο οποίο η ακύρωση μιας θυγατρικής κορουτίνας δεν ακυρώνει τις υπόλοιπες

Τι είναι το CoroutineScope στην Kotlin;

CoroutineScope — είναι μια θεμελιώδης διεπαφή από τη βιβλιοθήκη kotlinx.coroutines, η οποία λειτουργεί ως δοχείο για κορουτίνες. Καθορίζει τα όρια ζωής των συνεταιριστικών διαδικασιών: όταν το scope τερματίζεται, όλες οι κορουτίνες εντός αυτού ακυρώνονται αυτόματα.

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

Η διεπαφή περιέχει μόνο ένα πεδίο — coroutineContext. Μέσω αυτού, το scope παρέχει τον διακομιστή (Dispatcher), την εργασία (Job), τον χειριστή εξαιρέσεων και άλλα στοιχεία του πλαισίου για όλες τις κορουτίνες που εκτελούνται σε αυτό.

Ρόλος στη βιβλιοθήκη kotlinx.coroutines

Όλες οι συναρτήσεις εκκίνησης κορουτινών — launch, async, runBlocking — είναι συναρτήσεις επέκτασης (extension functions) στο CoroutineScope. Αυτό σημαίνει ότι μπορούν να κληθούν μόνο με την παρουσία ενός αντικειμένου scope. Αυτός ο σχεδιασμός διασφαλίζει ότι κάθε κορουτίνα έχει έναν σαφώς καθορισμένο γονέα και κύκλο ζωής.

Πού εφαρμόζεται το CoroutineScope

Στο Android, κάθε αρχιτεκτονικό στοιχείο έχει το δικό του scope: viewModelScope για ViewModel, lifecycleScope για Activity/Fragment. Σε εφαρμογές διακομιστή, το scope μπορεί να συνδεθεί με ένα HTTP αίτημα ή με μια ομάδα συνδέσεων βάσης δεδομένων.

Πώς λειτουργεί το CoroutineScope: Job και δομικός ανταγωνισμός

Η κατανόηση της εσωτερικής λειτουργίας του CoroutineScope απαιτεί εξοικείωση με την έννοια του Job και την αρχή του δομικού ανταγωνισμού.

Job — εργασία κορουτίνας

Κάθε κορουτίνα κατά την εκκίνηση επιστρέφει ένα αντικείμενο Job (ή Deferred για async). Το Job αντιπροσωπεύει μια εργασία με πεπερασμένο κύκλο ζωής: New, Active, Completing, Completed, Cancelling, Cancelled. Τα αντικείμενα Job σχηματίζουν μια δενδρική δομή:

  • Γονικό Job — το scope στο οποίο εκτελείται η κορουτίνα
  • Θυγατρικό Job — κάθε κορουτίνα που εκκινείται μέσω launch/async
  • Ακύρωση γονέα → ακύρωση όλων των θυγατρικών
  • Εξαίρεση σε θυγατρικό → ακύρωση γονέα (εκτός από supervisorScope)

Αρχή δομικού ανταγωνισμού

Δομικός ανταγωνισμός — βασική αρχιτεκτονική αρχή των Kotlin Coroutines, κατά την οποία η διάρκεια ζωής μιας κορουτίνας συνδέεται με τη διάρκεια ζωής του scope της. Αυτό έρχεται σε αντίθεση με το μοντέλο «fire-and-forget», όπου η κορουτίνα συνεχίζει να ζει μετά τον τερματισμό του scope. Πλεονεκτήματα του δομικού ανταγωνισμού:

  • Προβλέψιμος κύκλος ζωής — όταν το scope τερματίζεται, όλες οι κορουτίνες είναι εγγυημένα σταματημένες
  • Αυτόματος χειρισμός σφαλμάτων — η εξαίρεση σε οποιαδήποτε θυγατρική κορουτίνα διαδίδεται στο scope
  • Απουσία διαρροών — καμία κορουτίνα δεν παραμένει σε λειτουργία μετά τον τερματισμό του scope
  • Σαφής ιεραρχία — ο κώδικας αντικατοπτρίζει τη λογική δομή των παράλληλων λειτουργιών

Κύκλος ζωής του CoroutineScope

Όταν καλείται η scope.cancel(), το Job του scope μεταβαίνει σε κατάσταση Cancelled, η οποία ακυρώνει αναδρομικά όλα τα θυγατρικά Job. Μετά την ακύρωση, το scope μπορεί να επαναχρησιμοποιηθεί μόνο με τη δημιουργία ενός νέου στιγμιότυπου CoroutineScope.

Δημιουργία και ρύθμιση του CoroutineScope

Μπορείτε να δημιουργήσετε ένα CoroutineScope μέσω της συνάρτησης εργοστασίου ή μέσω υλοποίησης της διεπαφής στην κλάση σας. Ας εξετάσουμε και τις δύο προσεγγίσεις.

Συνάρτηση εργοστασίου CoroutineScope()

kotlin
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

scope.launch {
    println("Εκτελείται σε ${Thread.currentThread().name}")
}

Η συνάρτηση εργοστασίου δέχεται ένα CoroutineContext και δημιουργεί ένα scope με το καθορισμένο πλαίσιο. Στο παράδειγμα χρησιμοποιείται Dispatchers.Default για εργασίες έντασης CPU και SupervisorJob, το οποίο απομονώνει τις εξαιρέσεις μεταξύ των θυγατρικών κορουτινών.

Υλοποίηση διεπαφής μέσω σύνθεσης

kotlin
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:

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // η κορουτίνα εκτελείται στο πεδίο DataLoader
        }
    }
}

Αυτή η προσέγγιση είναι βολική όταν η κλάση είναι η ίδια scope και θέλει να παρέχει μεθόδους εκκίνησης κορουτινών. Ωστόσο, να είστε προσεκτικοί: η κλάση κληρονομεί όλες τις μεθόδους του CoroutineScope, συμπεριλαμβανομένης της cancel, η οποία μπορεί να παραβιάσει την ενσωμάτωση.

GlobalScope έναντι custom CoroutineScope

GlobalScope — είναι ένα στιγμιότυπο singleton CoroutineScope για ολόκληρη την εφαρμογή. Η χρήση του σε production κώδικα δεν συνιστάται επίσημα.

Προβλήματα του GlobalScope

  • Απουσία δομικού ανταγωνισμού — οι κορουτίνες στο GlobalScope δεν συνδέονται με τον κύκλο ζωής του στοιχείου
  • Διαρροές μνήμης — μια κορουτίνα μπορεί να συνεχίσει να εκτελείται μετά το κλείσιμο του Activity/Fragment
  • Δυσκολία δοκιμών — το GlobalScope δεν μπορεί να αντικατασταθεί σε δοκιμές
  • Ανεξέλεγκτη κατανάλωση πόρων — πολλές κορουτίνες μπορεί να λειτουργούν περισσότερο από το αναμενόμενο

Πότε δικαιολογείται το GlobalScope

Η JetBrains επιτρέπει το GlobalScope μόνο σε σπάνια σενάρια: διεργασίες παρασκηνίου σε επίπεδο εφαρμογής που πρέπει να ζουν ακόμη και μετά το κλείσιμο όλων των Activity (για παράδειγμα, συγχρονισμός δεδομένων, αναλυτικά στοιχεία). Αλλά και σε αυτές τις περιπτώσεις, προτιμάται η δημιουργία ενός δικού σας scope με CoroutineScope(SupervisorJob()).

Σύσταση

Χρησιμοποιείτε πάντα custom CoroutineScope με ρητή διαχείριση κύκλου ζωής. Στο Android, αυτά είναι τα viewModelScope και lifecycleScope. Σε εφαρμογές διακομιστή, δημιουργήστε scope για κάθε αίτημα ή ομάδα συνδέσεων.

coroutineScope vs supervisorScope: ποια είναι η διαφορά

Και οι δύο συναρτήσεις είναι suspend-συναρτήσεις που δημιουργούν ένα προσωρινό scope για παράλληλες εργασίες, αλλά η συμπεριφορά τους σε εξαιρέσεις διαφέρει ριζικά.

ΧαρακτηριστικόcoroutineScopesupervisorScope
Συμπεριφορά σε σφάλμαΕξαίρεση σε θυγατρική κορουτίνα ακυρώνει όλες τις υπόλοιπεςΕξαίρεση σε θυγατρική κορουτίνα ΔΕΝ ακυρώνει τις υπόλοιπες
Διάδοση σφάλματοςΝαι, η πρώτη εξαίρεση διαδίδεται προς τα έξωΝαι, η πρώτη εξαίρεση διαδίδεται προς τα έξω
Προεπιλεγμένο JobJob() — θυγατρικές συνδεδεμένες με γονέαSupervisorJob() — θυγατρικές ανεξάρτητες μεταξύ τους
Τυπική περίπτωση χρήσηςΑτομική λειτουργία πολλών βημάτωνΑνεξάρτητες παράλληλες εργασίες (UI φορτώσεις)

Πότε να επιλέξετε coroutineScope

Χρησιμοποιήστε coroutineScope όταν πολλές παράλληλες λειτουργίες αποτελούν μια ενιαία ατομική λειτουργία. Για παράδειγμα, φόρτωση δεδομένων από τρεις διακομιστές: αν ένα αίτημα αποτύχει, τα υπόλοιπα δεν έχουν νόημα.

kotlin
suspend fun loadProductPage(): ProductPage = coroutineScope {
    val product = async { api.getProduct() }
    val reviews = async { api.getReviews() }
    ProductPage(product.await(), reviews.await())
}

Αν η getProduct ή η getReviews ρίξουν εξαίρεση — και οι δύο κορουτίνες ακυρώνονται και η εξαίρεση διαδίδεται στον καλούντα κώδικα.

Πότε να επιλέξετε supervisorScope

Χρησιμοποιήστε supervisorScope όταν οι παράλληλες λειτουργίες είναι ανεξάρτητες μεταξύ τους. Για παράδειγμα, φόρτωση δεδομένων προφίλ σε πολλές ανεξάρτητες ενότητες: αν η ενότητα συστάσεων αποτύχει, η κεφαλίδα προφίλ και η λίστα φίλων πρέπει να εμφανιστούν.

Συνηθισμένα λάθη κατά τη χρήση του CoroutineScope

Ας εξετάσουμε τα πιο συχνά λάθη προγραμματιστών κατά τη χρήση του CoroutineScope στην Kotlin.

Λάθος 1: Ξεχάσατε να ακυρώσετε το scope

Το πιο συνηθισμένο σενάριο διαρροής κορουτίνας — δημιουργία scope χωρίς κλήση cancel κατά τον τερματισμό του στοιχείου. Αν το scope δεν ακυρωθεί, οι κορουτίνες συνεχίζουν να λειτουργούν, κρατώντας αναφορές σε αντικείμενα. Στο Android, χρησιμοποιήστε viewModelScope ή lifecycleScope, τα οποία ακυρώνονται αυτόματα.

Λάθος 2: Χρήση GlobalScope σε Activity ή Fragment

Το GlobalScope αγνοεί τον κύκλο ζωής των Android στοιχείων. Μια κορουτίνα που εκκινήθηκε στο GlobalScope μετά το κλείσιμο του Activity θα συνεχίσει να εκτελείται και θα προσπαθήσει να ενημερώσει το UI — προκαλώντας σφάλμα. Χρησιμοποιείτε πάντα lifecycleScope για UI στοιχεία.

Λάθος 3: Επαναχρησιμοποίηση ακυρωμένου scope

Μετά την κλήση cancel() το scope δεν μπορεί να επαναχρησιμοποιηθεί — όλες οι κορουτίνες σε αυτό έχουν ήδη τερματιστεί. Δημιουργήστε ένα νέο στιγμιότυπο CoroutineScope μέσω της συνάρτησης εργοστασίου. Η Job() δεν υποστηρίζει επανενεργοποίηση.

Λάθος 4: Λανθασμένη ανάθεση διεπαφής CoroutineScope

Κατά την ανάθεση με by, η κλάση αποκτά τη δημόσια μέθοδο cancel(), η οποία μπορεί να κληθεί από οπουδήποτε, παραβιάζοντας την ενσωμάτωση. Αποθηκεύστε το scope ως ιδιωτικό πεδίο, αντί να αναθέτετε τη διεπαφή.

Συχνές ερωτήσεις

Σε τι διαφέρει το CoroutineScope από το CoroutineContext;

CoroutineScope — είναι μια διεπαφή που κατέχει ένα CoroutineContext και είναι υπεύθυνη για τον κύκλο ζωής των κορουτινών. CoroutineContext — είναι ένα σύνολο στοιχείων (διακομιστής, job, χειριστής σφαλμάτων) που καθορίζει «πώς» εκτελείται η κορουτίνα. Μία από τις διαφορές: το scope δημιουργεί κορουτίνες, το πλαίσιο διαχειρίζεται τη συμπεριφορά τους.

Μπορώ να δημιουργήσω CoroutineScope με SupervisorJob;

Ναι, αυτό είναι ένα τυπικό μοτίβο: CoroutineScope(Dispatchers.IO + SupervisorJob()). Το SupervisorJob αποτρέπει την αλυσιδωτή ακύρωση θυγατρικών κορουτινών όταν υπάρχει εξαίρεση σε μία από αυτές. Είναι χρήσιμο για ανεξάρτητες παράλληλες εργασίες όπου ένα σφάλμα σε μία δεν πρέπει να σταματήσει τις άλλες.

Πόσες κορουτίνες μπορεί να περιέχει ένα CoroutineScope;

Δεν υπάρχει περιορισμός στον αριθμό κορουτινών σε ένα scope — περιορίζονται μόνο από τη διαθέσιμη μνήμη και τις ρυθμίσεις του διακομιστή. Το πρακτικό όριο είναι συνήθως χιλιάδες ενεργές κορουτίνες σε ένα scope. Ωστόσο, ένας μεγάλος αριθμός κορουτινών μπορεί να υποδηλώνει αρχιτεκτονικά προβλήματα.

Πώς να δοκιμάσω κώδικα με CoroutineScope;

Ο σωστός τρόπος είναι να μεταβιβάζετε το scope στην κλάση μέσω κατασκευαστή ή να χρησιμοποιείτε runBlockingTest / runTest από το kotlinx-coroutines-test. Στις δοκιμές, μπορείτε να αντικαταστήσετε το scope με TestCoroutineDispatcher και να ελέγχετε την εκτέλεση των κορουτινών χειροκίνητα.

Μπορεί μια κορουτίνα να έχει το δικό της scope;

Όχι, το scope είναι ένα εξωτερικό δοχείο για την κορουτίνα. Η ίδια η κορουτίνα δεν είναι scope. Ωστόσο, μέσα σε μια κορουτίνα μπορείτε να δημιουργήσετε ένα νέο scope μέσω coroutineScope ή supervisorScope για παράλληλη εκκίνηση θυγατρικών κορουτινών.

Συμπεράσματα

  • CoroutineScope — διεπαφή με πεδίο coroutineContext, που καθορίζει τον κύκλο ζωής των συνεταιριστικών διαδικασιών που εκτελούνται σε αυτό
  • Δομικός ανταγωνισμός — η ακύρωση του scope ακυρώνει αυτόματα όλες τις θυγατρικές κορουτίνες, αποτρέποντας διαρροές μνήμης
  • Job και SupervisorJob — δύο τρόποι χειρισμού σφαλμάτων: αλυσιδωτή ακύρωση (Job) και απομονωμένα σφάλματα (SupervisorJob)
  • GlobalScope — δεν συνιστάται για production λόγω έλλειψης σύνδεσης με τον κύκλο ζωής
  • coroutineScope vs supervisorScope — ατομικές παράλληλες λειτουργίες έναντι ανεξάρτητων παράλληλων εργασιών
  • viewModelScope και lifecycleScope — έτοιμα scope για Android, που ακυρώνονται αυτόματα κατά τον τερματισμό του στοιχείου
  • Συνάρτηση εργοστασίου — ο προτιμώμενος τρόπος δημιουργίας scope μέσω CoroutineContext + ρητής κλήσης cancel

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης