Dispatchers Kotlin Coroutines میں CoroutineContext کے اجزاء ہیں جو coroutines کو انجام دینے کے لیے تھریڈز کا تعین کرتے ہیں: Main (UI تھریڈ)، IO (نیٹ ورک اور ڈسک)، Default (CPU گہرے کام) اور Unconfined (موجودہ تھریڈ)۔ ہر ڈسپیچر ایک خصوصی تھریڈ پول کا انتظام کرتا ہے جو کام کی ایک مخصوص قسم کے لیے بہتر بنایا گیا ہے۔ JetBrains گائیڈ، 2024 کے مطابق، صحیح ڈسپیچر کا انتخاب ایپلیکیشن کی کارکردگی اور استحکام کے لیے اہم ہے۔
اہم نکات
Dispatchers CoroutineDispatcher انٹرفیس کے نفاذ ہیں، جو CoroutineContext کے عناصر ہیں۔ وہ تعین کرتے ہیں کہ coroutine کس تھریڈ یا تھریڈ پول پر عمل میں لایا جائے گا۔ launch یا async کے ذریعے coroutine بناتے وقت، ڈسپیچر کو پہلے پیرامیٹر کے طور پر منتقل کیا جا سکتا ہے: launch(Dispatchers.IO) { ... }۔ اگر کوئی ڈسپیچر متعین نہیں کیا گیا ہے، تو یہ بیرونی CoroutineScope سے وراثت میں ملتا ہے۔
Kotlin چار بلٹ ان ڈسپیچر فراہم کرتا ہے: Main، IO، Default، Unconfined۔ ہر ڈسپیچر اپنا تھریڈ پول استعمال کرتا ہے جو آپریشن کی ایک مخصوص قسم کے لیے بہتر بنایا گیا ہے۔ صحیح ڈسپیچر کا انتخاب ایپلیکیشن کی کارکردگی کا تعین کرتا ہے: غلط انتخاب UI سست روی، بیکار CPU کور یا غیر موثر تھریڈ استعمال کا باعث بنتا ہے۔
| ڈسپیچر | تھریڈ پول | زیادہ سے زیادہ تھریڈز | استعمال |
|---|---|---|---|
| Dispatchers.Main | ایک (UI) | 1 | UI اپ ڈیٹس، LiveData، View |
| Dispatchers.IO | IO پول | 64 (limitedParallelism) | نیٹ ورک، فائلیں، DB |
| Dispatchers.Default | CPU پول | N کور | ترتیب دینا، پارسنگ، حساب |
| Dispatchers.Unconfined | موجودہ تھریڈ | N/A | درمیانی آپریشنز، ٹیسٹ |
Dispatchers.Main وہ ڈسپیچر ہے جو Android کے مرکزی تھریڈ پر coroutines کو انجام دیتا ہے۔ یہ UI سے متعلقہ آپریشنز کے لیے ڈیزائن کیا گیا ہے: TextView کو اپ ڈیٹ کرنا، notifyDataSetChanged کو کال کرنا، LiveData اور StateFlow کے ساتھ کام کرنا۔ Android میں، یہ ڈسپیچر Handler (Looper.getMainLooper()) کے ذریعے لاگو کیا جاتا ہے۔
// 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 ایک ڈسپیچر ہے جو I/O آپریشنز کے لیے بہتر بنایا گیا ہے: HTTP درخواستیں (Ktor، OkHttp)، فائلوں کو پڑھنا اور لکھنا، Room یا SQLDelight کے ساتھ کام کرنا۔ یہ پہلے سے طے شدہ طور پر 64 تھریڈز کا پول استعمال کرتا ہے، جو بوجھ کے تحت توسیع پذیر ہے۔ ہر نئی I/O درخواست حد تک پہنچنے تک ایک اضافی تھریڈ بنا سکتی ہے۔
بیک وقت I/O آپریشنز کی تعداد کو کنٹرول کرنے کے لیے، limitedParallelism() استعمال کریں۔ یہ فنکشن متوازی تھریڈز کی تعداد پر حد کے ساتھ ایک نیا ڈسپیچر بناتا ہے، بڑے پیمانے پر آپریشنز کے دوران پول کو ختم ہونے سے روکتا ہے۔
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 ڈسپیچر پر CPU گہرے کام غیر موثر ہیں — وہ I/O کے لیے مخصوص تھریڈز پر قبضہ کر لیتے ہیں، جس سے سسٹم کا تھرو پٹ کم ہو جاتا ہے۔
Dispatchers.Default پروسیسر کو لوڈ کرنے والے کمپیوٹیشن آپریشنز کے لیے ڈسپیچر ہے: ترتیب دینا، فلٹرنگ، JSON پارسنگ (Moshi، Kotlinx Serialization)، تصویری پروسیسنگ، حساب۔ پول کا سائز پروسیسر کور کی تعداد کے برابر ہے (لیکن 2 سے کم نہیں)۔ یہ سیاق و سباق کی تبدیلی کے بغیر زیادہ سے زیادہ CPU استعمال کو یقینی بناتا ہے۔
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
I/O آپریشنز کے لیے Dispatchers.Default استعمال نہ کریں — یہ CPU پول کے تھریڈز کو بلاک کر دے گا جو کمپیوٹیشنل کاموں پر کارروائی کر سکتے تھے۔ IO اور Default کو الگ کرنا سسٹم کے وسائل کے بہترین استعمال کی اجازت دیتا ہے: IO تھریڈز I/O کا انتظار کرتے ہیں، CPU تھریڈز مسلسل حساب میں مصروف رہتے ہیں۔
Dispatchers.Unconfined ایک خاص ڈسپیچر ہے جو coroutine کو کسی بھی پول سے منسلک نہیں کرتا۔ Coroutine اس تھریڈ میں عملدرآمد شروع کرتا ہے جہاں launch/async کو کال کیا گیا تھا، اور معطلی کے بعد اس تھریڈ میں دوبارہ شروع ہوتا ہے جس نے resume کو کال کیا۔ یہ رویہ درمیانی آپریشنز کے لیے موزوں ہے جنہیں مقررہ سیاق و سباق کی ضرورت نہیں ہوتی۔
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Before delay: ${Thread.currentThread().getName()}")
delay(500L)
println("After delay: ${Thread.currentThread().getName()}")
}
}
پروڈکشن کوڈ میں، Dispatchers.Unconfined شاذ و نادر ہی استعمال ہوتا ہے۔ اہم استعمال کے معاملات: دوسرے ڈسپیچر میں ڈیٹا منتقل کرنے سے پہلے ہلکی تبدیلیاں اور ٹیسٹ۔ پروڈکشن ورک لوڈز کے لیے، واضح ڈسپیچر استعمال کریں — Unconfined غیر متوقع ہے کیونکہ عملدرآمد کا تھریڈ resume کے نفاذ پر منحصر ہے۔
ڈسپیچر کا انتخاب کام کی قسم پر منحصر ہے: UI آپریشنز → Main، I/O-bound → IO، CPU-bound → Default، درمیانی → scope سے وراثت۔ Android کے لیے، coroutine کو اس ڈسپیچر پر شروع کرنے کی سفارش کی جاتی ہے جہاں مرکزی کام انجام دیا جاتا ہے، اور UI کو اپ ڈیٹ کرنے سے پہلے withContext کے ذریعے Main پر سوئچ کریں۔
پیچیدہ منظرناموں کے لیے، + آپریٹر استعمال کرکے ڈسپیچرز کو یکجا کریں: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler۔ یہ ایک متعین ڈسپیچر، غلطی سے نمٹنے اور الگ تھلگ Job درجہ بندی کے ساتھ CoroutineContext بناتا ہے۔
اکثر پوچھے گئے سوالات
Dispatchers.IO I/O-bound آپریشنز (I/O کا انتظار) کے لیے 64 تھریڈز تک کا پول استعمال کرتا ہے، جبکہ Dispatchers.Default کمپیوٹیشنل کاموں کے لیے CPU کور کی تعداد پر مبنی پول استعمال کرتا ہے۔ جب تھریڈز کی کمی ہوتی ہے، دونوں پول ایک دوسرے کے ساتھ تھریڈز شیئر کر سکتے ہیں۔
ہاں، سنگل تھریڈ کے لیے newSingleThreadContext() یا فکسڈ پول کے لیے newFixedThreadPoolContext() استعمال کریں۔ پروڈکشن کے لیے، موجودہ ڈسپیچرز کی بنیاد پر limitedParallelism() استعمال کریں — یہ نئے پول بنانے سے زیادہ کارآمد ہے۔
اگر Dispatchers.Main دستیاب نہیں ہے (مثال کے طور پر، JUnit ٹیسٹ یا بیک گراؤنڈ سروس میں)، تو ایک IllegalStateException پھینکا جاتا ہے۔ ٹیسٹ کے لیے TestCoroutineDispatcher اور بیک گراؤنڈ سروسز کے لیے Dispatchers.IO یا Default استعمال کریں۔
Dispatchers.IO.limitedParallelism(N) استعمال کریں، جہاں N زیادہ سے زیادہ متوازی تھریڈز کی تعداد ہے۔ یہ بڑے پیمانے پر درخواستوں کے دوران پول کو ختم ہونے سے روکتا ہے اور کنٹرول شدہ متوازیت فراہم کرتا ہے۔
Dispatchers.Unconfined درمیانی آپریشنز کے لیے موزوں ہے: دوسرے ڈسپیچر میں منتقل کرنے سے پہلے ہلکی ڈیٹا تبدیلیاں، ٹیسٹ منظرنامے۔ پروڈکشن Android کوڈ میں، معطلی کے بعد غیر متعینہ عملدرآمد تھریڈ کی وجہ سے اس کی سفارش نہیں کی جاتی ہے۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں