تكرار السؤال: …
الإجابة المختصرة
launch يطلق كوروتين بنمط 'أطلق وانسَ' (Fire-and-forget) ويعيد كائن Job؛ بينما async يطلق مهمة تعيد نتيجة متوقعة كـ Deferred<T> وتُسترجع عبر await().
الشرح
يُستخدم launch عندما لا نحتاج لقيمة معادة من الكوروتين (مثل تسجيل حدث أو حفظ سجل). أما async فيُستخدم عند الرغبة في حساب قيمة، وخصوصًا لتشغيل عدة طلبات متزامنة في الخلفية والانتظار حتى تكتمل جميعها عبر await() أو awaitAll(). لاحظ أن سلوك الاستثناءات يعتمد على موقع async: إذا كان ابنًا داخل scope عادي (مثل coroutineScope أو viewModelScope) فإن فشله يلغي الأب والإخوة فورًا وينتشر الخطأ حتى لو لم يُستدعَ await()؛ أما async الجذري (مثل GlobalScope.async أو داخل supervisorScope) فيحتفظ بالاستثناء داخل الـ Deferred ولا يظهر إلا عند await().
مثال بالكود
// تنفيذ طلبين شبكيين متزامنين لتقليل زمن الانتظار:
suspend fun fetchDashboardData(): Dashboard = coroutineScope {
val profileDeferred = async { api.getProfile() }
val statsDeferred = async { api.getStats() }
// ينتظر اكتمال الاثنين بالتوازي:
Dashboard(profileDeferred.await(), statsDeferred.await())
}أخطاء شائعة
- استخدام async بدلاً من launch دون استدعاء await()؛ ففي الـ scope الجذري أو داخل supervisorScope يُحتفظ بالخطأ داخل الـ Deferred ولا يظهر أبدًا، فلا تدرك حدوث أي مشكلة.
- استدعاء await() فور استدعاء async في سطر متتابع مباشرة، مما يلغي ميزة التوازي ويجعل الكود متسلسلاً بطيئًا.