العمل مع قوائم المهام في Kotlin: للمبتدئين
\nفي التطوير الحديث، غالبًا ما تنشأ الحاجة لتنفيذ العديد من المهام بشكل غير متزامن: معالجة التنزيلات، إرسال الطلبات إلى API، العمل مع قواعد البيانات. بدون تنظيم صحيح للكود، قد يؤدي ذلك إلى الفوضى - حيث سيتم تنفيذ المهام بترتيب غير متوقع، وسيتم تحميل موارد النظام بشكل زائد. تعمل قوائم المهام (task queues) على حل هذه المشكلة. في هذه المقالة، سنشرح كيفية إنشاء واستخدام قوائم المهام في Kotlin باستخدام coroutines و Channel. المادة موجهة للمطورين المبتدئين الذين لديهم معرفة أساسية بـ Kotlin.
\n\nما هي قائمة المهام ولماذا نحتاجها؟
\nقائمة المهام هي بنية بيانات تعمل وفق مبدأ FIFO (First In, First Out - "من يدخل أولاً يخرج أولاً"). تقوم بإضافة مهمة إلى نهاية القائمة، ويقوم معالج خاص (worker) باستخراجها من البداية وتنفيذها. هذا يسمح بـ:
\n- \n
- تنظيم التنفيذ. المهام لا تتعارض مع بعضها البعض. \n
- التحكم في الحمل. يمكنك تحديد عدد المهام التي يتم تنفيذها في وقت واحد. \n
- زيادة استجابة التطبيق. العمليات الثقيلة لا تمنع الخيط الرئيسي. \n
في Kotlin، الأداة الرئيسية للعمل مع القوائم غير المتزامنة هي coroutines ومكونها Channel. هذه خيوط خفيفة الوزن لا تستهلك الكثير من الموارد.
إعداد المشروع للعمل مع coroutines
\nقبل كتابة الكود، تأكد من أن مشروعك يحتوي على التبعيات اللازمة. إذا كنت تستخدم Gradle، أضف إلى ملف build.gradle.kts (أو build.gradle):
dependencies {\n implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")\n implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // لنظام Android\n}\nالآن أنت مستعد لإنشاء قائمتك الأولى.
\n\nإنشاء قائمة بسيطة باستخدام Channel
\nChannel هو في الأساس قائمة جاهزة يمكنها نقل البيانات بين coroutines. لنلق نظرة على مثال أساسي: لدينا منتج (producer) يقوم بتوليد المهام، ومستهلك (consumer) يقوم بتنفيذها.
import kotlinx.coroutines.*\nimport kotlinx.coroutines.channels.Channel\n\nfun main() = runBlocking {\n val channel = Channel<String>() // إنشاء قناة للنصوص\n\n // المنتج: يرسل المهام\n launch {\n val tasks = listOf("تحميل ملف", "معالجة صورة", "إرسال تقرير")\n for (task in tasks) {\n println("تم إرسال المهمة: $task")\n channel.send(task) // إضافة إلى القائمة\n delay(500) // محاكاة الوقت بين المهام\n }\n channel.close() // إغلاق القناة بعد إرسال جميع المهام\n }\n\n // المستهلك: ينفذ المهام\n launch {\n for (task in channel) { // قراءة المهام من القائمة\n println("جارٍ التنفيذ: $task")\n delay(1000) // محاكاة وقت التنفيذ\n println("تم الانتهاء: $task")\n }\n }\n\n println("الخيط الرئيسي يواصل العمل...")\n delay(5000) // انتظار الانتهاء\n}\nماذا يحدث هنا؟
\n- \n
Channel<String>()ينشئ قائمة غير مانعة (non-blocking). \n send()يضيف عنصرًا،for (task in channel)يقرأها بالترتيب. \n - تقوم القناة تلقائيًا بحظر coroutine المستهلك إذا كانت القائمة فارغة، والمنتج إذا كانت القائمة ممتلئة (الافتراضي السعة 0، أي rendezvous). \n
سيكون مخرجات البرنامج متسلسلة: يتم تنفيذ المهام واحدة تلو الأخرى بترتيب الإضافة.
\n\nإدارة عدة عمال (workers) والتخزين المؤقت (buffering)
\nغالبًا ما تتطلب التطبيقات الحقيقية معالجة متوازية. لنفترض أن لدينا 10 مهام ونريد معالجتها بـ