runBlocking — คืออะไร, บริดจ์บล็อกกิงและทำงานอย่างไร

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

runBlocking — Coroutine Builder ใน Kotlin ที่บล็อกเธรดปัจจุบันจนกว่า coroutine ที่ส่งผ่านจะเสร็จสมบูรณ์ แตกต่างจาก launch และ async มันไม่ใช่ฟังก์ชัน suspend และสามารถเรียกใช้จากโค้ดปกติ (บล็อกกิง) ตามเอกสาร JetBrains, 2024 runBlocking ทำหน้าที่เป็นสะพานเชื่อมระหว่างโลกซิงโครนัสและอะซิงโครนัส ทำให้สามารถเริ่ม coroutine จากฟังก์ชัน main และการทดสอบ

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

  • runBlocking — บล็อกกิงบิลเดอร์ที่สร้าง CoroutineScope และรอให้ coroutine เสร็จสมบูรณ์
  • การบล็อกเธรด — runBlocking ยึดเธรดปัจจุบันไว้จนกว่า coroutine และ coroutine ย่อยทั้งหมดจะเสร็จสมบูรณ์
  • จุดเข้า — main(), การทดสอบ JUnit และสะพานระหว่างโค้ดบล็อกกิงและอะซิงโครนัส
  • ห้ามบนเธรดหลัก Android — การเรียก runBlocking บนเธรด UI ทำให้เกิด ANR
  • ทางเลือก — lifecycleScope, viewModelScope, TestCoroutineDispatcher สำหรับ Android

runBlocking คืออะไร?

runBlocking เป็นฟังก์ชัน Kotlin ที่สร้าง CoroutineScope ใหม่และเรียกใช้ coroutine ที่ส่งผ่าน โดยบล็อกเธรดปัจจุบันจนกว่าจะเสร็จสมบูรณ์ แตกต่างจาก Coroutine Builder อื่นๆ ทั้งหมด runBlocking ไม่ใช่ฟังก์ชัน suspend และสามารถเรียกใช้จากโค้ดซิงโครนัสปกติ ลายเซ็นของ runBlocking รับ CoroutineContext และบล็อก suspend และส่งกลับผลลัพธ์ชนิด T

kotlin
public fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T
): T

runBlocking เริ่ม event-loop ใหม่บนเธรดปัจจุบัน เมื่อ coroutine เรียกฟังก์ชัน suspend (เช่น delay() หรือ await()) runBlocking จะบล็อกเธรดและเรียกใช้ coroutine อื่นที่กำหนดไว้บนเธรดเดียวกันจนกว่า coroutine ที่ถูกระงับจะกลับมาทำงานต่อ นี่คือ การบล็อกแบบร่วมมือ — เธรดไม่ว่างเฉย แต่ประมวลผล coroutine อื่น

runBlocking ทำงานอย่างไร

กลไกภายในของ runBlocking ขึ้นอยู่กับ event-loop: เมื่อเรียกฟังก์ชัน suspend runBlocking จะหยุดการทำงานของบล็อกปัจจุบันชั่วคราวและเรียกใช้ coroutine อื่นจากคิว เมื่อฟังก์ชัน suspend เสร็จสมบูรณ์ การทำงานจะกลับมาดำเนินต่อ วงจรนี้จะดำเนินต่อไปจนกว่า coroutine ทั้งหมดจะสิ้นสุด

Event-loop ภายใต้ฝาครอบ

runBlocking ใช้พูลเธรดเดียวของตัวเองในการเรียกใช้ coroutine แตกต่างจาก Dispatchers.IO หรือ Default runBlocking ไม่เปลี่ยนเธรด — มันประมวลผล coroutine ทั้งหมดบนเธรดปัจจุบัน โดยสลับการทำงานของพวกมัน นี่คือ บิลเดอร์เดียว ที่รับประกันการทำงานบนเธรดเดียวกัน

kotlin
fun main() {
    val threadName = Thread.currentThread().getName()
    println("ก่อน runBlocking บน $threadName")

    val result = runBlocking {
        println("ภายใน runBlocking บน ${Thread.currentThread().getName()}")
        delay(500L)
        "Done"
    }

    println("หลัง runBlocking: $result")
}

ผลลัพธ์จะแสดงว่าคำสั่ง println ทั้งสามคำสั่งทำงานบนเธรดเดียวกัน runBlocking ไม่เปลี่ยนเธรด แต่จัดระเบียบมัลติทาสก์แบบร่วมมือภายในเธรดเดียวโดยใช้ event-loop

เมื่อใดควรใช้ runBlocking

runBlocking มีเหตุผลในสามสถานการณ์: จุดเข้า main() ในแอปพลิเคชันคอนโซล การทดสอบหน่วยของฟังก์ชัน suspend และสะพาน — การเรียกโค้ด suspend จากไลบรารีที่ใช้ callback หรือบล็อกกิง ในโค้ด Android โปรดักชัน การใช้บนเธรดหลักเป็นสิ่ง ต้องห้ามโดยเด็ดขาด

สถานการณ์การใช้งานได้ความเสี่ยง
main() ของแอปพลิเคชันคอนโซลได้ไม่มี — เป็นจุดเข้า เธรดไม่บล็อก UI
การทดสอบ JUnitได้น้อยที่สุด — การทดสอบเป็นซิงโครนัสตามนิยาม
เธรด UI Androidไม่ได้ANR, แล็ก, อินเทอร์เฟซค้าง
Callback → Coroutineได้ ด้วยความระมัดระวังการบล็อกพูลเธรดกับการดำเนินการนาน

สำหรับการทดสอบ Android ให้ใช้ kotlinx-coroutines-test กับ TestDispatcher แทน runBlocking สิ่งนี้ให้การควบคุมเวลา การทำความสะอาดอัตโนมัติ และการแยกการทดสอบ

ทางเลือกของ runBlocking

ในสถานการณ์ส่วนใหญ่ runBlocking สามารถและควรเปลี่ยนด้วยทางเลือกอะซิงโครนัส สำหรับ Android คือ viewModelScope, lifecycleScope หรือ CoroutineScope กับ dispatcher ที่เหมาะสม สำหรับการทดสอบ — TestCoroutineDispatcher และ runTest

kotlin
    // ไม่ดี: runBlocking บนเธรดหลัก Android
runBlocking(Dispatchers.Main) {
    val result = networkApi.fetchData()
    textView.setText(result)
}

// ดี: lifecycleScope
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
    textView.setText(result)
}

สำหรับการทดสอบ ทางเลือกคือ runTest จากไลบรารี kotlinx-coroutines-test มันสร้าง TestCoroutineScope พร้อมเวลาเสมือน ทำให้สามารถทดสอบความล่าช้าโดยไม่ต้องรอจริง สิ่งนี้ช่วยเร่งการทดสอบและทำให้เป็นเชิงกำหนด

ตัวอย่างการใช้ runBlocking

สถานการณ์ที่พบบ่อยที่สุดคือการทดสอบฟังก์ชัน suspend runBlocking ในการทดสอบช่วยให้รอผลลัพธ์ของ coroutine แบบซิงโครนัสโดยไม่เปลี่ยนสถาปัตยกรรม สถานการณ์ที่สองคือไลบรารีที่มี API callback ซึ่งฟังก์ชัน suspend ถูกเรียกจากบริบทบล็อกกิงผ่าน runBlocking

kotlin
// ทดสอบฟังก์ชัน suspend ด้วย runBlocking
class RepositoryTest {
    @Test
    fun `fetchUser returns correct data`() {
        val repository = UserRepository(FakeApi())

        val result = runBlocking {
            repository.fetchUser("123")
        }

        assertEquals("John", result.name)
        assertEquals("john@test.com", result.email)
    }
}

สำหรับสะพานระหว่างโลก callback และ suspend ให้ใช้ CompletableDeferred ร่วมกับ runBlocking แทน callbacks — สิ่งนี้ทำให้ห่วงโซ่การดำเนินการอะซิงโครนัสง่ายขึ้นและปรับปรุงความสามารถในการอ่านโค้ด

อันตรายจากการใช้งานผิดวิธี

การใช้ runBlocking อย่างไม่เหมาะสมเป็นหนึ่งในข้อผิดพลาดทั่วไปเมื่อเปลี่ยนจากวิธีการบล็อกกิงไปยัง coroutine ปัญหาหลัก: การเรียกบนเธรดหลัก Android การซ้อน runBlocking การใช้ภายในฟังก์ชันอะซิงโครนัส และการเริ่มดำเนินการนานผ่าน runBlocking

  • ANR — runBlocking บนเธรดหลัก Android บล็อกการเรนเดอร์ UI นานกว่า 5 วินาที
  • Deadlock — runBlocking ที่ซ้อนกันภายใน coroutine บนเธรดเดียวกันนำไปสู่การบล็อกซึ่งกันและกัน
  • ความสับสนของ dispatcher — Dispatchers.Main ภายใน runBlocking บนเธรดพื้นหลังไม่มี Looper และโยนข้อยกเว้น
  • หน่วยความจำรั่ว — runBlocking ไม่ถูกยกเลิกอัตโนมัติเมื่อ Activity/Fragment ถูกทำลาย

กฎทอง: runBlocking คือ สะพาน ไม่ใช่สิ่งทดแทน ใช้มันเพียงเพื่อเชื่อมต่อโลกบล็อกกิงและไม่บล็อกกิง สำหรับงานอื่นๆ ทั้งหมด ให้ใช้ launch, async หรือ lifecycleScope

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

ทำไม runBlocking บล็อกเธรดในขณะที่บิลเดอร์อื่นไม่บล็อก?

runBlocking เป็นบิลเดอร์เดียวที่ไม่ใช่ฟังก์ชัน suspend มันเริ่ม event-loop บนเธรดปัจจุบันและไม่คืนการควบคุมจนกว่า coroutine ทั้งหมดจะเสร็จสมบูรณ์ launch และ async คืนการควบคุมทันที โดยเรียกใช้ coroutine ในพื้นหลัง

สามารถใช้ runBlocking ใน Android ViewModel ได้หรือไม่?

ไม่แนะนำ ViewModel มี viewModelScope ในตัวที่จัดการ coroutine โดยอัตโนมัติและยกเลิกเมื่อถูกทำลาย runBlocking ใน ViewBlocking บล็อกเธรดและไม่ตอบสนองต่อการยกเลิกวงจรชีวิต

ใช้อะไรแทน runBlocking ในการทดสอบหน่วย?

ใช้ runTest จากไลบรารี kotlinx-coroutines-test มันให้ TestCoroutineScope พร้อมการควบคุมเวลาเสมือน การยกเลิกอัตโนมัติ และการทำงานเชิงกำหนด

event-loop ใน runBlocking คืออะไร?

Event-loop คือวงจรประมวลผลเหตุการณ์ภายใน runBlocking เมื่อ coroutine ถูกระงับ (เช่น delay()) event-loop จะสลับไปเรียกใช้ coroutine อื่นที่พร้อมบนเธรดเดียวกัน สิ่งนี้สร้างภาพลวงตาของมัลติทาสก์โดยไม่ต้องเปลี่ยนเธรด

จะเกิดอะไรขึ้นเมื่อเรียก runBlocking ภายใน runBlocking?

runBlocking ที่ซ้อนกัน บนเธรดเดียวกันสร้าง deadlock — บล็อกภายนอกรอบล็อกภายใน แต่ภายในไม่สามารถเริ่มได้จนกว่าภายนอกจะเสร็จสิ้น บนเธรดต่างกันอนุญาตแต่ไม่แนะนำอย่างยิ่งเนื่องจากความซับซ้อนในการดีบัก

สรุป

  • runBlocking — บล็อกกิง Coroutine Builder สะพานระหว่างโค้ดบล็อกกิงและอะซิงโครนัส
  • Event-loop runBlocking ประมวลผล coroutine แบบร่วมมือบนเธรดเดียวโดยไม่เปลี่ยน
  • สถานการณ์ที่อนุญาต — main(), การทดสอบ JUnit, สะพานจากไลบรารี callback
  • สถานการณ์ต้องห้าม — เธรด UI Android, การเรียกซ้อน, การดำเนินการนาน
  • ทางเลือก — lifecycleScope, viewModelScope, runTest สำหรับการทดสอบ
  • ความเสี่ยง ANR — runBlocking บนเธรดหลักทำให้แอปค้างหลัง 5 วินาที
  • สำหรับโค้ด Android โปรดักชัน ใช้บิลเดอร์อะซิงโครนัส — runBlocking ไม่ได้ออกแบบมาสำหรับ UI

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

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

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

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