async — Coroutine Builder στο Kotlin που εκκινεί ένα συνημμένο πρόγραμμα και επιστρέφει ένα αντικείμενο Deferred<T> για τη λήψη του αποτελέσματος μιας ασύγχρονης λειτουργίας. Το async επιτρέπει την παράλληλη εκτέλεση πολλαπλών εργασιών και τη συνάθροιση των αποτελεσμάτων τους μέσω του await(). Σύμφωνα με τον οδηγό της JetBrains, 2024, το async είναι ο προτιμώμενος τρόπος οργάνωσης παράλληλων υπολογισμών σε συνημμένες λειτουργίες.
Κύρια Σημεία
async — είναι μια συνάρτηση επέκτασης του CoroutineScope που δημιουργεί ένα συνημμένο πρόγραμμα με επιστροφή αποτελέσματος. Σε αντίθεση με το launch, το async επιστρέφει Deferred<T> — ένα αντικείμενο που αντιπροσωπεύει μια μελλοντική τιμή. Το συνημμένο πρόγραμμα που εκκινείται μέσω async εκτελείται ταυτόχρονα με άλλα συνημμένα προγράμματα, επιτρέποντας τον παραλληλισμό υπολογισμών.
Το async χρησιμοποιείται όταν το αποτέλεσμα μιας ασύγχρονης λειτουργίας χρειάζεται για περαιτέρω επεξεργασία: φόρτωση δεδομένων από πολλαπλές πηγές, επεξεργασία παρτίδας συλλογών, παράλληλα αιτήματα σε διάφορα API. Deferred δημιουργείται αμέσως και το αποτέλεσμα γίνεται διαθέσιμο μετά την ολοκλήρωση του συνημμένου προγράμματος.
import kotlinx.coroutines.*
suspend fun String.delayedValue(): String {
delay(1000L)
return this
}
fun main() = runBlocking {
val deferred: Deferred<String> = async { "Hello".delayedValue() }
println(deferred.await())
}
Η υπογραφή του async είναι παρόμοια με το launch: οι ίδιες παράμετροι CoroutineContext, CoroutineStart και μπλοκ suspend. Η διαφορά είναι στον τύπο επιστροφής — Deferred<T> αντί για Job, όπου T είναι ο τύπος του αποτελέσματος που επιστρέφεται από την τελευταία έκφραση του μπλοκ.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — διεπαφή που κληρονομεί το Job και προσθέτει τις μεθόδους await(), getCompleted(), getCompletionExceptionOrNull(). Η await() είναι συνάρτηση suspend και μπορεί να κληθεί μόνο εντός ενός συνημμένου προγράμματος ή άλλης συνάρτησης suspend. Η getCompleted() — μη αποκλείουσα μέθοδος που πετάει εξαίρεση αν το συνημμένο πρόγραμμα δεν έχει ακόμη ολοκληρωθεί.
Το κύριο πλεονέκτημα του async είναι η δυνατότητα παράλληλης εκκίνησης εργασιών. Αντί για σειριακή αναμονή για κάθε λειτουργία (συνολικός χρόνος = άθροισμα χρόνων), το async εκκινεί τα συνημμένα προγράμματα ταυτόχρονα, μειώνοντας τον συνολικό χρόνο στον χρόνο της μεγαλύτερης λειτουργίας.
Τυπικό σενάριο — φόρτωση προφίλ χρήστη, ρυθμίσεων και ιστορικού παραγγελιών με τρία παράλληλα async αιτήματα. Μετά την ολοκλήρωση και των τριών, τα αποτελέσματα συνδυάζονται σε ένα ενιαίο μοντέλο δεδομένων.
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
val profile = async { api.getProfile(userId) }
val settings = async { api.getSettings(userId) }
val orders = async { api.getRecentOrders(userId) }
Dashboard(
profile = profile.await(),
settings = settings.await(),
orders = orders.await()
)
}
Η συνάρτηση coroutineScope εκκινεί τα θυγατρικά συνημμένα προγράμματα και αναμένει την ολοκλήρωσή τους πριν επιστρέψει το αποτέλεσμα. Ολόκληρο το μπλοκ εκτελείται παράλληλα, όχι σειριακά.
Το Deferred υποστηρίζει διάφορες μεθόδους για εργασία με το αποτέλεσμα. Εκτός από την await(), υπάρχουν μέθοδοι για έλεγχο κατάστασης, λήψη της έτοιμης τιμής και συνάθροιση πολλαπλών Deferred μέσω βοηθητικών συναρτήσεων του Kotlin Coroutines.
| Μέθοδος | Περιγραφή | Suspend; |
|---|---|---|
| await() | Αναστέλλει το συνημμένο πρόγραμμα έως ότου ληφθεί το αποτέλεσμα | Ναι |
| getCompleted() | Επιστρέφει το αποτέλεσμα αν το συνημμένο πρόγραμμα ολοκληρώθηκε (αλλιώς Exception) | Όχι |
| getCompletionExceptionOrNull() | Επιστρέφει την εξαίρεση σε περίπτωση σφάλματος ή null | Όχι |
| isCompleted | Ελέγχει αν το συνημμένο πρόγραμμα ολοκληρώθηκε | Όχι |
Για τη συνάθροιση μιας λίστας Deferred χρησιμοποιείται το awaitAll() — μια συνάρτηση που αναστέλλει την εκτέλεση έως ότου ολοκληρωθούν όλα τα Deferred στη συλλογή. Αν τουλάχιστον ένα ολοκληρώθηκε με σφάλμα, το awaitAll() πετάει εξαίρεση.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
Η επιλογή μεταξύ async και launch εξαρτάται από το αν χρειάζεται το αποτέλεσμα εκτέλεσης του συνημμένου προγράμματος. Το launch επιστρέφει Job και είναι κατάλληλο για λειτουργίες fire-and-forget, το async επιστρέφει Deferred και χρησιμοποιείται για εργασίες που επιστρέφουν δεδομένα. Από άποψη σύνταξης και παραμέτρων, οι κατασκευαστές είναι πανομοιότυποι.
Σημαντικός κανόνας: μην χρησιμοποιείτε ποτέ async για fire-and-forget. Αν το αποτέλεσμα του συνημμένου προγράμματος δεν χρειάζεται, χρησιμοποιήστε launch. Το async δημιουργεί επιπλέον επιβάρυνση για τη δημιουργία Deferred, που δεν δικαιολογείται σε ένα τέτοιο σενάριο.
Στο Android, το async χρησιμοποιείται για παράλληλες λειτουργίες εντός ViewModel και UseCases. Τυπικά σενάρια: ταυτόχρονη φόρτωση προφίλ και ροής ειδήσεων, λήψη καιρού από πολλές μετεωρολογικές υπηρεσίες, συγχρονισμός δεδομένων παρτίδας με τον διακομιστή.
class HomeViewModel : ViewModel() {
fun loadHomeScreen() {
viewModelScope.launch {
val userData = async(Dispatchers.IO) { repository.getUserData() }
val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
val notifications = async(Dispatchers.IO) { repository.getNotifications() }
val state = HomeState(
user = userData.await(),
news = newsFeed.await(),
unread = notifications.await()
)
_uiState.update { state }
}
}
}
Συνιστάται να τυλίγετε την ομάδα async σε coroutineScope ή supervisorScope για απομόνωση σφαλμάτων. Το supervisorScope επιτρέπει τη συνέχιση εκτέλεσης των υπόλοιπων async συνημμένων προγραμμάτων ακόμη και αν παρουσιαστεί σφάλμα σε ένα από αυτά — χρήσιμο για μη κρίσιμες λειτουργίες.
Συχνές Ερωτήσεις
Μπορεί, αλλά είναι άσκοπο. Το async χωρίς await() εκκινεί το συνημμένο πρόγραμμα, αλλά το αποτέλεσμά του χάνεται. Αν το αποτέλεσμα δεν χρειάζεται, χρησιμοποιήστε launch — δεν δημιουργεί Deferred και είναι πιο αποδοτικό σε μνήμη και απόδοση.
Η εξαίρεση μέσα στο async αποθηκεύεται στο Deferred. Η κλήση await() προωθεί αυτήν την εξαίρεση. Για έλεγχο χωρίς προώθηση, χρησιμοποιήστε getCompletionExceptionOrNull(). Κατά τη χρήση coroutineScope, το σφάλμα ακυρώνει όλα τα θυγατρικά συνημμένα προγράμματα.
awaitAll() — συνάρτηση επέκτασης για Iterable<Deferred<T>> που αναστέλλει το συνημμένο πρόγραμμα έως ότου ολοκληρωθούν όλα τα Deferred. Επιστρέφει λίστα αποτελεσμάτων. Αν τουλάχιστον ένα Deferred ολοκληρωθεί με σφάλμα, προωθεί την εξαίρεση και ακυρώνει τα υπόλοιπα.
Ναι. Το async είναι συνάρτηση επέκτασης CoroutineScope, δεσμευμένη στο scope. Το GlobalScope.async δημιουργεί ένα συνημμένο πρόγραμμα χωρίς σύνδεση με τον κύκλο ζωής — μπορεί να εκτελείται επ' αόριστον. Στο Android, το GlobalScope δεν συνιστάται λόγω κινδύνου διαρροών μνήμης.
Ο αριθμός υπαγορεύεται από τον διανομέα: το Dispatchers.IO υποστηρίζει έως 64 ταυτόχρονα νήματα, το Dispatchers.Default — ανάλογα με τον αριθμό πυρήνων CPU. Για μαζικές λειτουργίες, χρησιμοποιήστε περιορισμένο παραλληλισμό μέσω Semaphore ή mapNotNull με awaitAll.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης