Dispatchers — مفاهیم کلیدی، انواع dispatcherها و نحوه کار آنها

نویسنده: IT Sectr منتشر شده: 2026-06-22 زمان مطالعه: 9 دقیقه

Dispatchers در Kotlin Coroutines — اجزای CoroutineContext هستند که رشته‌های اجرای هم‌روال‌ها را تعیین می‌کنند: Main (رشته UI)، IO (شبکه و دیسک)، Default (وظایف سنگین CPU) و Unconfined (رشته جاری). هر dispatcher یک استخر رشته تخصصی بهینه‌سازی شده برای نوع خاصی از کار را مدیریت می‌کند. به گفته راهنمای JetBrains، 2024، انتخاب dispatcher مناسب برای عملکرد و پایداری برنامه حیاتی است.

نکات اصلی

  • Dispatchers — dispatcherهای از پیش تعریف‌شده هم‌روال‌ها که توزیع وظایف را بین رشته‌ها مدیریت می‌کنند
  • Dispatchers.Main — رشته UI اندروید، برای به‌روزرسانی رابط و کار با View
  • Dispatchers.IO — استخر برای درخواست‌های شبکه، خواندن/نوشتن فایل‌ها و پایگاه داده (تا ۶۴ رشته)
  • Dispatchers.Default — استخر برای وظایف سنگین CPU بر اساس تعداد هسته‌های پردازنده
  • Dispatchers.Unconfined — وراثت رشته جاری، بدون جابجایی

Dispatchers چیست؟

Dispatchers — پیاده‌سازی‌های رابط CoroutineDispatcher هستند که عناصری از CoroutineContext می‌باشند. آنها تعیین می‌کنند که هم‌روال روی کدام رشته یا استخر رشته اجرا شود. هنگام ایجاد هم‌روال از طریق launch یا async، dispatcher می‌تواند به عنوان پارامتر اول ارسال شود: launch(Dispatchers.IO) { ... }. اگر dispatcher مشخص نشود، از CoroutineScope خارجی به ارث می‌رسد.

Kotlin چهار dispatcher داخلی ارائه می‌دهد: Main، IO، Default، Unconfined. هر dispatcher از استخر رشته مخصوص خود استفاده می‌کند که برای نوع خاصی از عملیات بهینه‌سازی شده است. انتخاب صحیح dispatcher عملکرد برنامه را تعیین می‌کند: انتخاب اشتباه منجر به لگ در رابط، بیکار ماندن هسته‌های CPU یا مصرف ناکارآمد رشته‌ها می‌شود.

Dispatcerاستخر رشتهحداکثر رشتهکاربرد
Dispatchers.Mainیک (UI)۱به‌روزرسانی UI، LiveData، View
Dispatchers.IOاستخر IO۶۴ (limitedParallelism)شبکه، فایل‌ها، پایگاه داده
Dispatchers.Defaultاستخر CPUN هستهمرتب‌سازی، تجزیه، محاسبات
Dispatchers.Unconfinedرشته جارینداردعملیات میانی، تست‌ها

Dispatchers.Main: رشته UI

Dispatchers.Main — dispatcherای که هم‌روال‌ها را روی رشته اصلی اندروید اجرا می‌کند. برای عملیات مرتبط با رابط طراحی شده است: به‌روزرسانی TextView، فراخوانی notifyDataSetChanged، کار با LiveData و StateFlow. در اندروید این dispatcher از طریق Handler (Looper.getMainLooper()) پیاده‌سازی شده است.

kotlin
// تغییر صحیح به Main برای به‌روزرسانی UI
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

اگر هم‌روال قبلاً روی Main-dispatcher است، withContext(Dispatchers.Main) اضافی سرباری ایجاد نمی‌کند — dispatcher رشته جاری را بررسی کرده و جابجایی را رد می‌کند. withContext روش ترجیحی برای جابجایی بین dispatcherها است.

Dispatchers.IO: شبکه و عملیات دیسک

Dispatchers.IO — dispatcher بهینه‌سازی شده برای عملیات ورودی-خروجی: درخواست‌های HTTP (Ktor, OkHttp)، خواندن و نوشتن فایل‌ها، کار با Room یا SQLDelight. به طور پیش‌فرض از استخری با ۶۴ رشته استفاده می‌کند که تحت بار مقیاس‌پذیر است. هر درخواست IO جدید می‌تواند یک رشته اضافی تا رسیدن به حد مجاز ایجاد کند.

محدود کردن هم‌روندی

برای کنترل تعداد عملیات هم‌زمان IO از limitedParallelism() استفاده کنید. این تابع یک dispatcher جدید با محدودیت تعداد رشته‌های موازی ایجاد می‌کند و از تخلیه استخر در عملیات انبوه جلوگیری می‌کند.

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

// بارگذاری ۱۰۰ فایل با محدودیت ۴ عملیات هم‌زمان
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

از IO-dispatcher برای تمام عملیاتی که هم‌روال زمان خود را در انتظار (I/O-bound) سپری می‌کند استفاده کنید. وظایف سنگین CPU روی IO-dispatcher ناکارآمد هستند — آنها رشته‌های در نظر گرفته شده برای ورودی-خروجی را اشغال کرده و ظرفیت عبور سیستم را کاهش می‌دهند.

Dispatchers.Default: وظایف سنگین CPU

Dispatchers.Default — dispatcher برای عملیات محاسباتی سنگین پردازنده: مرتب‌سازی، فیلتر کردن، تجزیه JSON (Moshi, Kotlinx Serialization)، پردازش تصاویر، محاسبات. اندازه استخر برابر با تعداد هسته‌های پردازنده است (اما نه کمتر از ۲). این حداکثر بارگذاری CPU را بدون جابجایی زمینه تضمین می‌کند.

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

از Dispatchers.Default برای عملیات IO استفاده نکنید — این کار رشته‌های استخر CPU را که می‌توانستند وظایف محاسباتی را پردازش کنند مسدود می‌کند. تقسیم به IO و Default امکان استفاده بهینه از منابع سیستم را فراهم می‌کند: رشته‌های IO منتظر ورودی-خروجی هستند، رشته‌های CPU دائماً مشغول محاسبات هستند.

Dispatchers.Unconfined: رشته جاری

Dispatchers.Unconfined — یک dispatcher ویژه که هم‌روال را به هیچ استخری متصل نمی‌کند. هم‌روال اجرا را در همان رشته‌ای آغاز می‌کند که launch/async در آن فراخوانی شده و پس از تعلیق در رشته‌ای که resume را فراخوانی کرده ادامه می‌یابد. این رفتار برای عملیات میانی که نیاز به زمینه ثابت ندارند مناسب است.

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("قبل از تأخیر: ${Thread.currentThread().getName()}")
        delay(500L)
        println("بعد از تأخیر: ${Thread.currentThread().getName()}")
    }
}

در کد production از Dispatchers.Unconfined به ندرت استفاده می‌شود. موارد اصلی: تبدیل‌های سبک قبل از ارسال داده به dispatcher دیگر و تست‌ها. برای بار production از dispatcherهای صریح استفاده کنید — Unconfined غیرقابل پیش‌بینی است زیرا رشته اجرا به پیاده‌سازی resume بستگی دارد.

نحوه انتخاب dispatcher

انتخاب dispatcher با نوع وظیفه تعیین می‌شود: عملیات UI → Main، I/O-bound → IO، CPU-bound → Default، میانی → وراثت از scope. برای اندروید توصیه می‌شود هم‌روال را روی dispatcherای که کار اصلی در آن انجام می‌شود راه‌اندازی کنید و قبل از به‌روزرسانی UI از طریق withContext به Main جابجا شوید.

  • عملیات IO را روی Main-dispatcher اجرا نکنید — این کار UI را مسدود می‌کند
  • وظایف سنگین CPU را روی IO-dispatcher اجرا نکنید — این کار رشته‌های استخر IO را ناکارآمد مصرف می‌کند
  • از limitedParallelism استفاده کنید برای کنترل هم‌روندی در عملیات انبوه IO
  • dispatcher را جابجا کنید با withContext، نه با ایجاد scope جدید
  • dispatcher را از scope به ارث ببرید اگر هم‌روال به استخر خاصی نیاز ندارد

برای سناریوهای پیچیده dispatcherها را با عملگر + ترکیب کنید: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. این یک CoroutineContext با dispatcher مشخص، مدیریت خطا و سلسله‌مراتب Job ایزوله ایجاد می‌کند.

سوالات متداول

تفاوت Dispatchers.IO و Dispatchers.Default چیست؟

Dispatchers.IO از استخری تا ۶۴ رشته برای عملیات I/O-bound (انتظار ورودی-خروجی) استفاده می‌کند و Dispatchers.Default از استخری به تعداد هسته‌های CPU برای وظایف محاسباتی. در کمبود رشته، هر دو استخر می‌توانند رشته‌ها را با یکدیگر به اشتراک بگذارند.

آیا می‌توانم dispatcher خود را در Kotlin Coroutines ایجاد کنم؟

بله، از newSingleThreadContext() برای تک‌رشته‌ای یا newFixedThreadPoolContext() برای استخر ثابت استفاده کنید. برای production از limitedParallelism() بر اساس dispatcherهای موجود استفاده کنید — این کارآمدتر از ایجاد استخرهای جدید است.

هنگام فراخوانی Dispatchers.Main در رشته پس‌زمینه چه اتفاقی می‌افتد؟

اگر Dispatchers.Main در دسترس نباشد (مثلاً در تست JUnit یا سرویس پس‌زمینه)، IllegalStateException پرتاب می‌شود. برای تست‌ها از TestCoroutineDispatcher و برای سرویس‌های پس‌زمینه از Dispatchers.IO یا Default استفاده کنید.

چگونه تعداد عملیات هم‌زمان IO را محدود کنم؟

از Dispatchers.IO.limitedParallelism(N) استفاده کنید که در آن N حداکثر تعداد رشته‌های موازی است. این کار از تخلیه استخر در درخواست‌های انبوه جلوگیری کرده و هم‌روندی کنترل‌شده‌ای فراهم می‌کند.

چه زمانی از Dispatchers.Unconfined استفاده کنیم؟

Dispatchers.Unconfined برای عملیات میانی مناسب است: تبدیل‌های سبک داده قبل از ارسال به dispatcher دیگر و سناریوهای تست. در کد production اندروید به دلیل نامشخص بودن رشته اجرا پس از تعلیق توصیه نمی‌شود.

خلاصه

  • Dispatchers — اجزای CoroutineContext تعیین‌کننده رشته‌های اجرای هم‌روال‌ها
  • Dispatchers.Main — برای عملیات UI اندروید، یک رشته اصلی
  • Dispatchers.IO — برای عملیات شبکه و دیسک، استخر تا ۶۴ رشته
  • Dispatchers.Default — برای محاسبات سنگین CPU، استخر به تعداد هسته‌ها
  • Dispatchers.Unconfined — بدون اتصال به رشته، برای عملیات میانی
  • withContext — مکانیسم اصلی جابجایی بین dispatcherها درون هم‌روال
  • انتخاب dispatcher اشتباه منجر به لگ UI یا استفاده ناکارآمد از منابع می‌شود

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید