Dispatchers — แนวคิดหลัก ประเภทของตัวจัดส่งและวิธีการทำงาน

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-22 เวลาอ่าน: 9 นาที

Dispatchers ใน Kotlin Coroutines เป็นส่วนประกอบของ CoroutineContext ที่กำหนดเธรดสำหรับการดำเนินการ coroutines: Main (เธรด UI), IO (เครือข่ายและดิสก์), Default (งานที่ใช้ CPU สูง) และ Unconfined (เธรดปัจจุบัน) ตัวจัดส่งแต่ละตัวจัดการกลุ่มเธรดเฉพาะทางที่ปรับให้เหมาะสมกับงานประเภทใดประเภทหนึ่ง ตามคู่มือ JetBrains, 2024 การเลือกตัวจัดส่งที่ถูกต้องมีความสำคัญต่อประสิทธิภาพและความเสถียรของแอปพลิเคชัน

ประเด็นสำคัญ

  • Dispatchers — ตัวจัดส่ง coroutine ที่กำหนดไว้ล่วงหน้าซึ่งจัดการการกระจายงานระหว่างเธรด
  • Dispatchers.Main — เธรด UI ของ Android สำหรับอัปเดตอินเทอร์เฟซและทำงานกับ View
  • Dispatchers.IO — กลุ่มสำหรับคำขอเครือข่าย การอ่าน/เขียนไฟล์ และ DB (สูงสุด 64 เธรด)
  • Dispatchers.Default — กลุ่มสำหรับงานที่ใช้ CPU สูงตามจำนวนคอร์ของโปรเซสเซอร์
  • Dispatchers.Unconfined — สืบทอดเธรดปัจจุบัน โดยไม่สลับ

Dispatchers คืออะไร?

Dispatchers คือการนำอินเทอร์เฟซ CoroutineDispatcher ไปใช้ ซึ่งเป็นองค์ประกอบของ CoroutineContext พวกมันกำหนดว่า coroutine จะดำเนินการบนเธรดหรือกลุ่มเธรดใด เมื่อสร้าง coroutine ผ่าน launch หรือ async ตัวจัดส่งสามารถส่งเป็นพารามิเตอร์แรกได้: launch(Dispatchers.IO) { ... } หากไม่ได้ระบุตัวจัดส่งไว้ มันจะสืบทอดจาก CoroutineScope ภายนอก

Kotlin มีตัวจัดส่งในตัวสี่ตัว: Main, IO, Default, Unconfined ตัวจัดส่งแต่ละตัวใช้กลุ่มเธรดของตัวเองที่ปรับให้เหมาะสมกับการดำเนินการประเภทใดประเภทหนึ่งโดยเฉพาะ การเลือกตัวจัดส่งที่ถูกต้อง เป็นตัวกำหนดประสิทธิภาพของแอปพลิเคชัน: การเลือกที่ผิดนำไปสู่การหน่วงของ UI, คอร์ CPU ว่างงาน หรือการใช้เธรดที่ไม่มีประสิทธิภาพ

ตัวจัดส่งกลุ่มเธรดเธรดสูงสุดการใช้งาน
Dispatchers.Mainหนึ่ง (UI)1อัปเดต UI, LiveData, View
Dispatchers.IOกลุ่ม IO64 (limitedParallelism)เครือข่าย, ไฟล์, DB
Dispatchers.Defaultกลุ่ม CPUN คอร์การเรียงลำดับ, การแยกวิเคราะห์, การคำนวณ
Dispatchers.Unconfinedเธรดปัจจุบันไม่มีการดำเนินการกลาง, การทดสอบ

Dispatchers.Main: เธรด UI

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 คือตัวจัดส่งที่ปรับให้เหมาะสมสำหรับการดำเนินการ I/O: คำขอ HTTP (Ktor, OkHttp), การอ่านและเขียนไฟล์, การทำงานกับ Room หรือ SQLDelight มันใช้กลุ่ม 64 เธรด โดยค่าเริ่มต้น ปรับขนาดได้ภายใต้โหลด คำขอ I/O ใหม่แต่ละรายการสามารถสร้างเธรดเพิ่มเติมได้จนกว่าจะถึงขีดจำกัด

การจำกัดความขนาน

เพื่อควบคุมจำนวนการดำเนินการ I/O พร้อมกัน ให้ใช้ 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) งานที่ใช้ CPU สูงบนตัวจัดส่ง IO ไม่มีประสิทธิภาพ — พวกมันครอบครองเธรดที่ intended สำหรับ I/O ทำให้ ปริมาณงาน ของระบบลดลง

Dispatchers.Default: งานที่ใช้ CPU สูง

Dispatchers.Default คือตัวจัดส่งสำหรับการดำเนินการคำนวณที่โหลดโปรเซสเซอร์: การเรียงลำดับ, การกรอง, การแยกวิเคราะห์ JSON (Moshi, Kotlinx Serialization), การประมวลผลภาพ, การคำนวณ ขนาดกลุ่มเท่ากับจำนวนคอร์ของโปรเซสเซอร์ (แต่ไม่น้อยกว่า 2) ซึ่งช่วยให้มั่นใจถึงการใช้งาน CPU สูงสุดโดยไม่ต้องสลับบริบท

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

อย่าใช้ Dispatchers.Default สำหรับการดำเนินการ I/O — สิ่งนี้จะบล็อกเธรดกลุ่ม CPU ที่สามารถประมวลผลงานคำนวณได้ การแยก IO และ Default ช่วยให้ ใช้ทรัพยากร ระบบอย่างเหมาะสมที่สุด: เธรด IO รอ I/O, เธรด 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

วิธีการเลือกตัวจัดส่ง

การเลือกตัวจัดส่งขึ้นอยู่กับประเภทงาน: การดำเนินการ UI → Main, I/O-bound → IO, CPU-bound → Default, กลาง → สืบทอดจากขอบเขต สำหรับ Android แนะนำให้เริ่ม coroutine บนตัวจัดส่งที่ทำงานหลัก และสลับไปยัง Main ผ่าน withContext ก่อนอัปเดต UI

  • อย่าเรียกใช้ การดำเนินการ I/O บนตัวจัดส่ง Main — สิ่งนี้บล็อก UI
  • อย่าเรียกใช้ งานที่ใช้ CPU สูงบนตัวจัดส่ง IO — สิ่งนี้กินเธรดกลุ่ม IO อย่างไม่มีประสิทธิภาพ
  • ใช้ limitedParallelism เพื่อควบคุมความพร้อมกันระหว่างการดำเนินการ I/O จำนวนมาก
  • สลับตัวจัดส่ง โดยใช้ withContext ไม่ใช่โดยการสร้างขอบเขตใหม่
  • สืบทอดตัวจัดส่ง จากขอบเขตหาก coroutine ไม่ต้องการกลุ่มเฉพาะ

สำหรับสถานการณ์ที่ซับซ้อน ให้รวมตัวจัดส่งโดยใช้ตัวดำเนินการ +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler สิ่งนี้สร้าง CoroutineContext ด้วยตัวจัดส่งที่ระบุ การจัดการข้อผิดพลาด และลำดับชั้น Job ที่แยกออกจากกัน

คำถามที่พบบ่อย

Dispatchers.IO และ Dispatchers.Default แตกต่างกันอย่างไร?

Dispatchers.IO ใช้กลุ่มสูงสุด 64 เธรดสำหรับการดำเนินการ I/O-bound (รอ I/O) ในขณะที่ Dispatchers.Default ใช้กลุ่มตามจำนวนคอร์ CPU สำหรับงานคำนวณ เมื่อเธรดขาดแคลน ทั้งสองกลุ่มสามารถแชร์เธรดซึ่งกันและกันได้

ฉันสามารถสร้างตัวจัดส่งของตัวเองใน Kotlin Coroutines ได้หรือไม่?

ได้ ใช้ newSingleThreadContext() สำหรับเธรดเดียวหรือ newFixedThreadPoolContext() สำหรับกลุ่มคงที่ สำหรับการผลิต ให้ใช้ limitedParallelism() บนพื้นฐานของตัวจัดส่งที่มีอยู่ — สิ่งนี้มีประสิทธิภาพมากกว่าการสร้างกลุ่มใหม่

จะเกิดอะไรขึ้นเมื่อเรียก Dispatchers.Main จากเธรดพื้นหลัง?

หาก Dispatchers.Main ไม่พร้อมใช้งาน (เช่น ในการทดสอบ JUnit หรือบริการพื้นหลัง) IllegalStateException จะถูกโยน ใช้ TestCoroutineDispatcher สำหรับการทดสอบ และ Dispatchers.IO หรือ Default สำหรับบริการพื้นหลัง

จะจำกัดจำนวนการดำเนินการ I/O แบบขนานได้อย่างไร?

ใช้ Dispatchers.IO.limitedParallelism(N) โดยที่ N คือจำนวนเธรดแบบขนานสูงสุด สิ่งนี้ป้องกันการหมดของกลุ่มระหว่างคำขอจำนวนมากและให้ความขนานที่ควบคุมได้

เมื่อใดควรใช้ Dispatchers.Unconfined?

Dispatchers.Unconfined เหมาะสำหรับการดำเนินการกลาง: การแปลงข้อมูลแบบเบาก่อนส่งไปยังตัวจัดส่งอื่น สถานการณ์การทดสอบ ในโค้ด Android การผลิต ไม่แนะนำเนื่องจากเธรดการดำเนินการไม่แน่นอนหลังจากการระงับ

สรุป

  • Dispatchers — ส่วนประกอบ CoroutineContext ที่กำหนดเธรดการดำเนินการ coroutine
  • Dispatchers.Main — สำหรับการดำเนินการ UI ของ Android, หนึ่งเธรดหลัก
  • Dispatchers.IO — สำหรับการดำเนินการเครือข่ายและดิสก์, กลุ่มสูงสุด 64 เธรด
  • Dispatchers.Default — สำหรับการคำนวณที่ใช้ CPU สูง, กลุ่มตามจำนวนคอร์
  • Dispatchers.Unconfined — ไม่มีการผูกเธรด, สำหรับการดำเนินการกลาง
  • withContext — กลไกหลักในการสลับระหว่างตัวจัดส่งภายใน coroutine
  • การเลือกตัวจัดส่งผิดนำไปสู่การหน่วงของ UI หรือการใช้ทรัพยากรที่ไม่มีประสิทธิภาพ

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม