launch/join مقابل async/await في Kotlin: أيهما تختار في 2025؟

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

launch/join مقابل async/await في Kotlin: أيهما تختار في 2025؟ تحليل كامل مع أمثلة

\n\n

مرحبًا، زميلي! إذا كنت قد كتبت أي وقت مضى كوروتينات في Kotlin، فبالتأكيد تساءلت: "ما الفرق بين launch و async؟ ولماذا نحتاج إلى join الغامض؟". هذا السؤال هو من أشهر الأسئلة على Stack Overflow، وحصد آلاف المشاهدات. وليس من دون سبب: للوهلة الأولى، تقوم كلتا الأداتين بنفس الشيء - تشغيل مهمة في الخلفية. لكن الشيطان، كما هو الحال دائمًا، يكمن في التفاصيل.

\n\n

في عام 2025، عندما أصبحت Kotlin coroutines هي الاتجاه السائد في تطوير Android والواجهة الخلفية، فإن فهم هذه الاختلافات ليس مجرد ترف، بل ضرورة لكتابة كود نظيف وآمن وفعال. في هذه المقالة، لن نقوم فقط بسرد الحقائق الجافة. سنقوم بتحليل سيناريوهات حقيقية، وننظر إلى الكود، ونستخلص أفضل الممارسات التي يمكنك تطبيقها بثقة في مشاريعك الآن.

\n\n

هيا بنا!

\n\n

launch: "شغّل وانسَ"

\n\n

الدالة launch هي، على الأرجح، أبسط طريقة لتشغيل كوروتين. فهي تنشئ كوروتينًا جديدًا لا يُرجع نتيجة. مهمته هي القيام ببعض العمل: تحديث واجهة المستخدم، إرسال سجل، كتابة بيانات إلى ملف. نوع القيمة المُرجعة هو Job. الأمر يشبه إعطاء مهمة لساعي: "قم بتوصيل الطرد وعد، لا حاجة لتقرير".

\n\n

تتيح لك طريقة join انتظار اكتمال هذا الكوروتين. إنها تعلق تنفيذ الكوروتين الحالي حتى يكتمل job الذي استدعى join.

\n\n

لنلقِ نظرة على مثال. لنفترض أننا بحاجة إلى تحميل صورتين بالتوازي، ثم دمجهما. باستخدام launch و join سيبدو الأمر كالتالي:

\n\n
import kotlinx.coroutines.*\n\nsuspend fun loadImage(url: String): Image {\n    // محاكاة التحميل\n    delay(1000)\n    return Image(url)\n}\n\ndata class Image(val url: String)\n\nsuspend fun loadTwoImagesWithLaunch() = coroutineScope {\n    var image1: Image? = null\n    var image2: Image? = null\n\n    val job1 = launch {\n        image1 = loadImage("https://example.com/img1.png")\n    }\n\n    val job2 = launch {\n        image2 = loadImage("https://example.com/img2.png")\n    }\n\n    // ننتظر كلتا المهمتين\n    job1.join()\n    job2.join()\n\n    // الآن تم تحميل image1 و image2 بشكل مضمون\n    println("Images loaded: $image1 and $image2")\n}
\n\n

ما المهم هنا؟ نحن نستخدم متغيرات قابلة للتغيير (var) لحفظ النتيجة. هذا ليس جميلًا جدًا وقد يؤدي إلى مشاكل مع الخيوط، على الرغم من أن الكوروتينات تعمل ضمن خيط واحد. لكن العيب الرئيسي هو أننا لا نستطيع استخدام launch لإرجاع قيمة مباشرة.

\n\n

async: "شغّل واحصل على النتيجة"

\n\n

أما async فهو "درجة رجال الأعمال". إنه ينشئ كوروتينًا يُرجع نتيجة على شكل كائن Deferred<T>. الأمر يشبه طلب سيارة أجرة: تنتظر، وتحصل ليس فقط على حقيقة الرحلة، بل على نتيجة محددة - أنت في المكان. للحصول على هذه النتيجة، نستخدم await().

\n\n

نفس مثال تحميل الصور، ولكن باستخدام async/await، يصبح أكثر أناقة:

\n\n
import kotlinx.coroutines.*\n\nsuspend fun loadImage(url: String): Image {\n    delay(1000)\n    return Image(url)\n}\n\ndata class Image(val url: String)\n\nsuspend fun loadTwoImagesWithAsync() = coroutineScope {\n    val deferred1 = async { loadImage("https://example.com/img1.png") }\n    val deferred2 = async { loadImage("https://example.com/img2.png") }\n\n    // await() تعلق الكوروتين حتى تحصل على النتيجة\n    val image1 = deferred1.await()\n    val image2 = deferred2.await()\n\n    println("Images loaded: $image1 and $image2")\n}
\n\n

هل تشعر بالفرق؟ لا var، ولا إدارة يدوية. كل شيء نظيف،

المدونات

توصيات الكتب