async — هو منشئ Coroutine Builder في Kotlin يقوم بتشغيل كوروتين ويعيد كائن Deferred<T> للحصول على نتيجة عملية غير متزامنة. يسمح async بتنفيذ مهام متعددة بالتوازي وتجميع نتائجها عبر await(). وفقًا لـ دليل JetBrains، 2024، يعتبر async الطريقة المفضلة لتنظيم الحسابيات المتوازية في الكوروتينات.
النقاط الرئيسية
async — هي دالة توسعة لـ CoroutineScope تقوم بإنشاء كوروتين يعيد نتيجة. على عكس launch، تعيد async Deferred<T> — كائنًا يمثل قيمة مستقبلية. يتم تشغيل الكوروتين المُطلَق عبر async بشكل متزامن مع الكوروتينات الأخرى، مما يسمح بتوزية الحسابيات.
يتم استخدام async عندما تكون نتيجة عملية غير متزامنة مطلوبة للمعالجة اللاحقة: تحميل البيانات من مصادر متعددة، معالجة المجموعات بالدفعات، طلبات متوازية لمختلف واجهات البرمجة. يتم إنشاء 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() | تعيد النتيجة إذا اكتملت الكوروتين (إلا ستثناء) | لا |
| 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 مرتبطة بالنطاق. يقوم GlobalScope.async بإنشاء كوروتين دون ارتباط بدورة الحياة — يمكن أن يعمل بشكل غير محدود. لا يوصى بـ GlobalScope في Android بسبب خطر التسرب.
يتحدد العدد بواسطة الموزع: يدعم Dispatchers.IO ما يصل إلى 64 مسار متزامنًا، و Dispatchers.Default — حتى عدد نواة وحدة المعالجة. للعمليات الضخمة، استخدم التوازي المحدود عبر Semaphore أو mapNotNull مع awaitAll.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا