مقدمة: لماذا يحتاج تطبيقك إلى قوائم الانتظار والمهام الخلفية
\n\nتخيل موقفًا: تقوم بتشغيل التطبيق، تضغط على زر "إرسال"، و... تتجمد الواجهة لمدة 10 ثوانٍ. المستخدم يشعر بالتوتر، ويضغط بإصبعه على الشاشة، بينما تطبيقك يحسب شيئًا بصمت في الخيط الرئيسي. هل يبدو هذا مألوفًا؟ إذا كانت الإجابة بنعم، فهذه المقالة لك.
\n\nفي عالم التطوير الحديث باستخدام Kotlin، قوائم الانتظار والمهام الخلفية ليست مجرد "ممارسة جيدة"، بل ضرورة. أي تطبيق يعمل مع الشبكة أو قاعدة البيانات أو الحسابات المعقدة يجب أن يكون قادرًا على معالجة المهام بشكل غير متزامن. وإلا، فستفقد المستخدمين وستحصل على تقييمات منخفضة في متجر التطبيقات.
\n\nفي هذه المقالة، سنشرح كيفية العمل مع قوائم الانتظار والمهام الخلفية في Kotlin. ستتعرف على Kotlin Coroutines و Channel و WorkManager وأدوات أخرى. ونعم، كل شيء سيكون مع أمثلة كود حقيقية يمكنك استخدامها فورًا في مشروعك.
\n\nما هي قائمة انتظار المهام ولماذا نحتاجها؟
\n\nقائمة انتظار المهام (task queue) هي بنية بيانات تسمح بتنظيم تنفيذ المهام بترتيب معين، غالبًا بشكل غير متزامن. تخيل طابورًا في صندوق السوبر ماركت: كل عميل (مهمة) ينتظر دوره، والصراف (العامل) يعالجهم واحدًا تلو الآخر.
\n\nفي البرمجة، تساعد قوائم الانتظار في:
\n- \n
- تجنب حظر الخيط الرئيسي؛ \n
- إدارة الحمل على النظام؛ \n
- ضمان ترتيب معالجة المهام؛ \n
- معالجة المهام مع إعادة المحاولة عند الفشل. \n
في Kotlin، يمكننا تنفيذ قوائم الانتظار بطرق مختلفة. الأكثر شيوعًا وحداثة هو عبر Kotlin Coroutines و Channel.
\n\nKotlin Coroutines: الأساس للمهام الخلفية
\n\nقبل الحديث عن قوائم الانتظار، دعنا نتذكر ما هي coroutines. Kotlin Coroutines هي آلية للبرمجة غير المتزامنة تسمح بكتابة كود غير محظور بأسلوب أمري.
\n\nإليك مثال بسيط لتشغيل مهمة خلفية:
\n\nimport kotlinx.coroutines.*\n\nfun main() = runBlocking {\n launch {\n delay(1000L)\n println("المهمة الخلفية مكتملة!")\n }\n println("الخيط الرئيسي يواصل العمل")\n}\n\nفي هذا المثال، يقوم launch بتشغيل coroutine جديدة يتم تنفيذها بالتوازي مع الكود الرئيسي. الكلمة المفتاحية delay هي تأخير غير محظور لا "يجمد" الخيط.
Dispatcher: أين يتم تنفيذ المهام
\n\nمن المهم فهم أي خيط يتم تنفيذ coroutines الخاصة بك عليه. لهذا يوجد Dispatcher:
\n\n- \n
Dispatchers.Main— لخيط واجهة المستخدم (Android)؛ \n Dispatchers.IO— لعمليات الإدخال والإخراج (الشبكة، قواعد البيانات)؛ \n Dispatchers.Default— للمهام كثيفة الاستخدام للمعالج (CPU)؛ \n Dispatchers.Unconfined— للحالات الخاصة. \n
مثال على الاستخدام:
\n\nimport kotlinx.coroutines.*\n\nfun main() = runBlocking {\n launch(Dispatchers.IO) {\n // عمل ثقيل مع الشبكة\n val data = fetchDataFromNetwork()\n withContext(Dispatchers.Main) {\n // تحديث واجهة المستخدم\n updateUI(data)\n }\n }\n}\n\nقوائم الانتظار مع Channel: المنتج-المستهلك
\n\nالآن ننتقل إلى الجزء الأكثر إثارة للاهتمام — تنفيذ قوائم الانتظار باستخدام Channel. Channel هو بدائية تزامن تسمح بنقل البيانات بين coroutines، وتعمل وفق مبدأ قائمة الانتظار.
النمط الكلاسيكي "المنتج-المستهلك" في Kotlin يبدو كالتالي:
\n\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.channels.*\n\nfun main() = runBlocking {\n val channel = Channel<String>()\n\n // المنتج\n launch {\n