Dispatchers — المفاهيم الأساسية، أنواع المُرسِلات وكيفية عملها

المؤلف: IT Sectr نُشر: 2026-06-22 وقت القراءة: 9 دق

Dispatchers في Kotlin Coroutines هي مكونات CoroutineContext التي تحدد الخيوط لتنفيذ coroutines: Main (خيط واجهة المستخدم)، IO (الشبكة والقرص)، Default (المهام كثيفة الاستخدام لوحدة المعالجة المركزية) و Unconfined (الخيط الحالي). يدير كل مُرسِل مجموعة خيوط متخصصة محسَّنة لنوع معين من العمل. وفقًا لدليل JetBrains، 2024، فإن اختيار المُرسِل الصحيح أمر بالغ الأهمية لأداء التطبيق واستقراره.

الخلاصة

  • Dispatchers — مُرسِلات coroutines محددة مسبقًا تدير توزيع المهام عبر الخيوط
  • Dispatchers.Main — خيط واجهة المستخدم في Android، لتحديث الواجهة والعمل مع View
  • Dispatchers.IO — مجموعة لطلبات الشبكة وقراءة/كتابة الملفات وقواعد البيانات (حتى 64 خيطًا)
  • Dispatchers.Default — مجموعة للمهام كثيفة الاستخدام لوحدة المعالجة المركزية حسب عدد أنوية المعالج
  • Dispatchers.Unconfined — يرث الخيط الحالي، بدون تبديل

ما هي Dispatchers؟

Dispatchers هي تطبيقات لواجهة CoroutineDispatcher، وهي عناصر من CoroutineContext. تحدد على أي خيط أو مجموعة خيوط سيتم تنفيذ coroutine. عند إنشاء coroutine عبر launch أو async، يمكن تمرير المُرسِل كأول معامل: launch(Dispatchers.IO) { ... }. إذا لم يتم تحديد مُرسِل، يتم توريثه من CoroutineScope الخارجي.

يوفر Kotlin أربعة مُرسِلات مدمجة: Main و IO و Default و Unconfined. يستخدم كل مُرسِل مجموعة الخيوط الخاصة به المحسَّنة لنوع معين من العمليات. اختيار المُرسِل الصحيح يحدد أداء التطبيق: الاختيار الخاطئ يؤدي إلى تباطؤ واجهة المستخدم أو خمول أنوية وحدة المعالجة المركزية أو استخدام غير فعال للخيوط.

المُرسِلمجموعة الخيوطأقصى عدد من الخيوطالاستخدام
Dispatchers.Mainواحد (UI)1تحديثات واجهة المستخدم، LiveData، View
Dispatchers.IOمجموعة IO64 (limitedParallelism)الشبكة، الملفات، قواعد البيانات
Dispatchers.Defaultمجموعة CPUN من الأنويةالفرز، التحليل، الحسابات
Dispatchers.Unconfinedالخيط الحاليغير متاحالعمليات الوسيطة، الاختبارات

Dispatchers.Main: خيط واجهة المستخدم

Dispatchers.Main هو المُرسِل الذي ينفذ coroutines على الخيط الرئيسي لنظام Android. وهو مصمم للعمليات المتعلقة بواجهة المستخدم: تحديث TextView، استدعاء notifyDataSetChanged، العمل مع LiveData و StateFlow. في Android، يتم تنفيذ هذا المُرسِل عبر Handler (Looper.getMainLooper()).

kotlin
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

إذا كان coroutine موجودًا بالفعل على المُرسِل Main، فإن withContext(Dispatchers.Main) الإضافي لا يُنشئ عبئًا — يتحقق المُرسِل من الخيط الحالي ويتخطى التبديل. withContext هو الطريقة المفضلة للتبديل بين المُرسِلات.

Dispatchers.IO: عمليات الشبكة والقرص

Dispatchers.IO هو مُرسِل محسَّن لعمليات الإدخال/الإخراج: طلبات HTTP (Ktor، OkHttp)، قراءة وكتابة الملفات، العمل مع Room أو SQLDelight. يستخدم مجموعة من 64 خيطًا افتراضيًا، قابلة للتوسع تحت الحمل. يمكن لكل طلب إدخال/إخراج جديد إنشاء خيط إضافي حتى الوصول إلى الحد الأقصى.

تحديد التوازي

للتحكم في عدد عمليات الإدخال/الإخراج المتزامنة، استخدم limitedParallelism(). تنشئ هذه الدالة مُرسِلاً جديدًا بحد أقصى لعدد الخيوط المتوازية، مما يمنع استنزاف المجموعة أثناء العمليات الجماعية.

kotlin
val limitedIo = Dispatchers.IO.limitedParallelism(4)

// Load 100 files with limit of 4 concurrent operations
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

استخدم مُرسِل IO لجميع العمليات حيث يقضي coroutine الوقت في الانتظار (I/O-bound). المهام كثيفة الاستخدام لوحدة المعالجة المركزية على مُرسِل IO غير فعالة — فهي تشغل خيوطًا مخصصة للإدخال/الإخراج، مما يقلل من الإنتاجية للنظام.

Dispatchers.Default: المهام كثيفة الاستخدام لوحدة المعالجة المركزية

Dispatchers.Default هو المُرسِل للعمليات الحسابية التي تُحمِّل المعالج: الفرز، التصفية، تحليل JSON (Moshi، Kotlinx Serialization)، معالجة الصور، الحسابات. حجم المجموعة يساوي عدد أنوية المعالج (ولكن لا يقل عن 2). يضمن ذلك أقصى استفادة من وحدة المعالجة المركزية دون تبديل السياق.

kotlin
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
    return withContext(Dispatchers.Default) {
        input
            .parallelStream()
            .map { transform(it) }
            .toList()
    }
}

لا تستخدم Dispatchers.Default لعمليات الإدخال/الإخراج — سيؤدي ذلك إلى حظر خيوط مجموعة وحدة المعالجة المركزية التي يمكنها معالجة المهام الحسابية. يسمح فصل IO و Default بـ الاستخدام الأمثل لموارد النظام: خيوط IO تنتظر الإدخال/الإخراج، وخيوط CPU مشغولة باستمرار بالحسابات.

Dispatchers.Unconfined: الخيط الحالي

Dispatchers.Unconfined هو مُرسِل خاص لا يربط coroutine بأي مجموعة. يبدأ coroutine التنفيذ في الخيط الذي تم استدعاء launch/async منه، وبعد التعليق يستأنف في الخيط الذي استدعى resume. هذا السلوك مناسب للعمليات الوسيطة التي لا تتطلب سياقًا ثابتًا.

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("Before delay: ${Thread.currentThread().getName()}")
        delay(500L)
        println("After delay: ${Thread.currentThread().getName()}")
    }
}

في كود الإنتاج، نادرًا ما يُستخدم Dispatchers.Unconfined. حالات الاستخدام الرئيسية: التحويلات الخفيفة قبل تمرير البيانات إلى مُرسِل آخر والاختبارات. لأعباء العمل الإنتاجية، استخدم مُرسِلات صريحة — Unconfined غير متوقع لأن خيط التنفيذ يعتمد على تنفيذ resume.

كيفية اختيار مُرسِل

يعتمد اختيار المُرسِل على نوع المهمة: عمليات واجهة المستخدم → Main، I/O-bound → IO، CPU-bound → Default، وسيطة → توريث من النطاق. بالنسبة لنظام Android، يُوصى بتشغيل coroutine على المُرسِل حيث يتم العمل الرئيسي، والتبديل إلى Main عبر withContext قبل تحديث واجهة المستخدم.

  • لا تقم بتشغيل عمليات الإدخال/الإخراج على المُرسِل Main — فهذا يحظر واجهة المستخدم
  • لا تقم بتشغيل المهام كثيفة الاستخدام لوحدة المعالجة المركزية على المُرسِل IO — فهذا يستهلك خيوط مجموعة IO بشكل غير فعال
  • استخدم limitedParallelism للتحكم في التوازي أثناء عمليات الإدخال/الإخراج الجماعية
  • بدّل المُرسِل باستخدام withContext، وليس عن طريق إنشاء نطاق جديد
  • ورِّث المُرسِل من النطاق إذا كان coroutine لا يتطلب مجموعة محددة

للسيناريوهات المعقدة، ادمج المُرسِلات باستخدام عامل التشغيل +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. يؤدي ذلك إلى إنشاء CoroutineContext مع مُرسِل محدد ومعالجة الأخطاء وتسلسل هرمي معزول لـ Job.

الأسئلة الشائعة

ما الفرق بين Dispatchers.IO و Dispatchers.Default؟

Dispatchers.IO يستخدم مجموعة تصل إلى 64 خيطًا للعمليات المرتبطة بالإدخال/الإخراج (انتظار الإدخال/الإخراج)، بينما Dispatchers.Default يستخدم مجموعة تعتمد على عدد أنوية وحدة المعالجة المركزية للمهام الحسابية. عندما تكون الخيوط شحيحة، يمكن للمجموعتين مشاركة الخيوط مع بعضهما البعض.

هل يمكنني إنشاء مُرسِل خاص بي في Kotlin Coroutines؟

نعم، استخدم newSingleThreadContext() لمجموعة خيط واحد أو newFixedThreadPoolContext() لمجموعة ثابتة. للإنتاج، استخدم limitedParallelism() بناءً على المُرسِلات الموجودة — وهذا أكثر كفاءة من إنشاء مجموعات جديدة.

ماذا يحدث عند استدعاء Dispatchers.Main من خيط خلفية؟

إذا كان Dispatchers.Main غير متاح (على سبيل المثال، في اختبار JUnit أو خدمة خلفية)، يتم طرح IllegalStateException. استخدم TestCoroutineDispatcher للاختبارات و Dispatchers.IO أو Default للخدمات الخلفية.

كيفية تحديد عدد عمليات الإدخال/الإخراج المتوازية؟

استخدم Dispatchers.IO.limitedParallelism(N)، حيث N هو أقصى عدد من الخيوط المتوازية. يمنع ذلك استنزاف المجموعة أثناء الطلبات الجماعية ويوفر توازيًا محكومًا.

متى يتم استخدام Dispatchers.Unconfined؟

Dispatchers.Unconfined مناسب للعمليات الوسيطة: تحويلات البيانات الخفيفة قبل تمريرها إلى مُرسِل آخر، سيناريوهات الاختبار. في كود إنتاج Android، لا يُوصى به بسبب خيط التنفيذ غير المحدد بعد التعليق.

الملخص

  • Dispatchers — مكونات CoroutineContext التي تحدد خيوط تنفيذ coroutines
  • Dispatchers.Main — لعمليات واجهة المستخدم في Android، خيط رئيسي واحد
  • Dispatchers.IO — لعمليات الشبكة والقرص، مجموعة تصل إلى 64 خيطًا
  • Dispatchers.Default — للحسابات كثيفة الاستخدام لوحدة المعالجة المركزية، مجموعة حسب عدد الأنوية
  • Dispatchers.Unconfined — بدون ربط بخيط، للعمليات الوسيطة
  • withContext — الآلية الرئيسية للتبديل بين المُرسِلات داخل coroutine
  • اختيار المُرسِل الخاطئ يؤدي إلى تباطؤ واجهة المستخدم أو استخدام غير فعال للموارد

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا