Dispatchers در Kotlin Coroutines — اجزای CoroutineContext هستند که رشتههای اجرای همروالها را تعیین میکنند: Main (رشته UI)، IO (شبکه و دیسک)، Default (وظایف سنگین CPU) و Unconfined (رشته جاری). هر dispatcher یک استخر رشته تخصصی بهینهسازی شده برای نوع خاصی از کار را مدیریت میکند. به گفته راهنمای JetBrains، 2024، انتخاب dispatcher مناسب برای عملکرد و پایداری برنامه حیاتی است.
نکات اصلی
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 | استخر CPU | N هسته | مرتبسازی، تجزیه، محاسبات |
| Dispatchers.Unconfined | رشته جاری | ندارد | عملیات میانی، تستها |
Dispatchers.Main — dispatcherای که همروالها را روی رشته اصلی اندروید اجرا میکند. برای عملیات مرتبط با رابط طراحی شده است: بهروزرسانی TextView، فراخوانی notifyDataSetChanged، کار با LiveData و StateFlow. در اندروید این dispatcher از طریق Handler (Looper.getMainLooper()) پیادهسازی شده است.
// تغییر صحیح به 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 — dispatcher بهینهسازی شده برای عملیات ورودی-خروجی: درخواستهای HTTP (Ktor, OkHttp)، خواندن و نوشتن فایلها، کار با Room یا SQLDelight. به طور پیشفرض از استخری با ۶۴ رشته استفاده میکند که تحت بار مقیاسپذیر است. هر درخواست IO جدید میتواند یک رشته اضافی تا رسیدن به حد مجاز ایجاد کند.
برای کنترل تعداد عملیات همزمان IO از limitedParallelism() استفاده کنید. این تابع یک dispatcher جدید با محدودیت تعداد رشتههای موازی ایجاد میکند و از تخلیه استخر در عملیات انبوه جلوگیری میکند.
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 — dispatcher برای عملیات محاسباتی سنگین پردازنده: مرتبسازی، فیلتر کردن، تجزیه JSON (Moshi, Kotlinx Serialization)، پردازش تصاویر، محاسبات. اندازه استخر برابر با تعداد هستههای پردازنده است (اما نه کمتر از ۲). این حداکثر بارگذاری CPU را بدون جابجایی زمینه تضمین میکند.
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 — یک dispatcher ویژه که همروال را به هیچ استخری متصل نمیکند. همروال اجرا را در همان رشتهای آغاز میکند که launch/async در آن فراخوانی شده و پس از تعلیق در رشتهای که resume را فراخوانی کرده ادامه مییابد. این رفتار برای عملیات میانی که نیاز به زمینه ثابت ندارند مناسب است.
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 با نوع وظیفه تعیین میشود: عملیات UI → Main، I/O-bound → IO، CPU-bound → Default، میانی → وراثت از scope. برای اندروید توصیه میشود همروال را روی dispatcherای که کار اصلی در آن انجام میشود راهاندازی کنید و قبل از بهروزرسانی UI از طریق withContext به Main جابجا شوید.
برای سناریوهای پیچیده dispatcherها را با عملگر + ترکیب کنید: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. این یک CoroutineContext با dispatcher مشخص، مدیریت خطا و سلسلهمراتب Job ایزوله ایجاد میکند.
سوالات متداول
Dispatchers.IO از استخری تا ۶۴ رشته برای عملیات I/O-bound (انتظار ورودی-خروجی) استفاده میکند و Dispatchers.Default از استخری به تعداد هستههای CPU برای وظایف محاسباتی. در کمبود رشته، هر دو استخر میتوانند رشتهها را با یکدیگر به اشتراک بگذارند.
بله، از newSingleThreadContext() برای تکرشتهای یا newFixedThreadPoolContext() برای استخر ثابت استفاده کنید. برای production از limitedParallelism() بر اساس dispatcherهای موجود استفاده کنید — این کارآمدتر از ایجاد استخرهای جدید است.
اگر Dispatchers.Main در دسترس نباشد (مثلاً در تست JUnit یا سرویس پسزمینه)، IllegalStateException پرتاب میشود. برای تستها از TestCoroutineDispatcher و برای سرویسهای پسزمینه از Dispatchers.IO یا Default استفاده کنید.
از Dispatchers.IO.limitedParallelism(N) استفاده کنید که در آن N حداکثر تعداد رشتههای موازی است. این کار از تخلیه استخر در درخواستهای انبوه جلوگیری کرده و همروندی کنترلشدهای فراهم میکند.
Dispatchers.Unconfined برای عملیات میانی مناسب است: تبدیلهای سبک داده قبل از ارسال به dispatcher دیگر و سناریوهای تست. در کد production اندروید به دلیل نامشخص بودن رشته اجرا پس از تعلیق توصیه نمیشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید