0Pricing
Kotlin Academy · บทเรียน

SupervisorJob กับ Job: การแยกความล้มเหลว

ใช้ SupervisorJob เพื่อป้องกันไม่ให้ความล้มเหลวของ coroutine ลูกหนึ่งยกเลิกพี่น้อง

SupervisorJob กับ Job: การแยกความล้มเหลว เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Kotlin Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

การเผยแพร่ความล้มเหลวของ Job ตามค่าเริ่มต้น

เมื่อใช้ Job ทั่วไป ความล้มเหลวของงานลูกจะยกเลิกงานแม่ ซึ่งจะยกเลิกงานพี่น้องทั้งหมดด้วย ความล้มเหลวเพียงครั้งเดียวจึงทำให้ลำดับชั้นทั้งหมดหยุดทำงาน

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            launch { delay(50); throw RuntimeException("child 1 failed") }
            launch { delay(1000); println("child 2 — never prints") }
        }
    } catch (e: RuntimeException) {
        println("Caught: ${e.message}")
    }
}

ภาพรวม SupervisorJob

SupervisorJob เปลี่ยนกฎดังกล่าว โดยความล้มเหลวของงานลูกจะไม่ยกเลิกงานพี่น้องหรือ งานแม่ แต่ละงานลูกจึงล้มเหลวได้อย่างเป็นอิสระ

import kotlinx.coroutines.*
fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(coroutineContext + supervisor)
    scope.launch { throw RuntimeException("child 1 failed") }
    scope.launch { delay(100); println("child 2 still runs") }
    delay(200)
    supervisor.cancel()
}

ตัวสร้าง supervisorScope

supervisorScope { } สร้างขอบเขตที่มี SupervisorJob เป็นงานแม่ นี่คือแนวทางตามแบบแผนสำหรับการเรียกใช้งานลูกที่เป็นอิสระต่อกัน

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val job1 = launch {
            throw RuntimeException("job1 failed")
        }
        val job2 = launch {
            delay(100)
            println("job2 succeeded")
        }
        job1.join()  // wait for job1 (it failed)
        job2.join()  // job2 is unaffected
    }
}

การดักจับความล้มเหลวของงานลูก

เมื่อใช้ supervisorScope งานลูกแต่ละงานจะจัดการข้อยกเว้นของตัวเอง ข้อยกเว้นที่ไม่ได้จัดการในงานลูกต้องถูกดักจับแยกกันในแต่ละงาน

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val result = async {
            delay(50)
            throw RuntimeException("async failed")
        }
        try {
            result.await()
        } catch (e: RuntimeException) {
            println("Caught from async: ${e.message}")
        }
        println("Scope continues")
    }
}

Job เทียบกับ SupervisorJob ตอนสร้างขอบเขต

ส่ง SupervisorJob() เมื่อสร้าง CoroutineScope สำหรับบริการหรือ ViewModel ที่ควรทำงานต่อได้แม้งานลูกบางงานจะล้มเหลว

import kotlinx.coroutines.*
class MyService {
    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
    fun startTask(name: String) {
        scope.launch {
            if (name == "fail") throw RuntimeException("$name failed")
            println("$name done")
        }
    }
    fun stop() = scope.cancel()
}
fun main() = runBlocking {
    val svc = MyService()
    svc.startTask("fail")
    svc.startTask("ok")
    delay(100)
    svc.stop()
}

viewModelScope ใช้ SupervisorJob

viewModelScope ของ Android ทำงานอยู่บน SupervisorJob การเรียกใช้งานที่ล้มเหลวหนึ่งครั้งจะไม่ยกเลิกโครูทีนอื่น ๆ ของ ViewModel

import kotlinx.coroutines.*
// Android ViewModel internals:
// val viewModelScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
// Each launch is independent — one failure doesn't crash the ViewModel
fun main() = runBlocking {
    println("viewModelScope = SupervisorJob + Main")
}

การแยกงานแบบขนานด้วย supervisorScope

ใช้ supervisorScope + async สำหรับงานแบบขนานที่บางงานอาจล้มเหลวได้โดยไม่กระทบงานอื่น

import kotlinx.coroutines.*
fun main() = runBlocking {
    val results = supervisorScope {
        val a = async { delay(50); "result-A" }
        val b = async { throw RuntimeException("B failed") }
        val c = async { delay(30); "result-C" }
        listOf(
            runCatching { a.await() },
            runCatching { b.await() },
            runCatching { c.await() }
        )
    }
    results.forEach { println(it) }
}

เมื่อใดควรใช้ Job ปกติ

ใช้ Job ปกติ (coroutineScope) เมื่อโครูทีนลูกทั้งหมดต้องทำงานสำเร็จร่วมกัน — หากมีงานหนึ่งล้มเหลว ควรยกเลิกทั้งกลุ่ม เปรียบได้กับธุรกรรมที่ต้องสำเร็จทั้งหมดหรือล้มเหลวทั้งหมด

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {  // regular Job
            val a = async { delay(50); "A" }
            val b = async { throw RuntimeException("B failed") }
            println(a.await())
            println(b.await())  // throws — cancels a too
        }
    } catch (e: RuntimeException) {
        println("Transaction failed: ${e.message}")
    }
}

การส่งต่อข้อยกเว้นใน supervisorScope

ใน supervisorScope ข้อยกเว้นจะถูกส่งต่อไปยัง ขอบเขต ก็ต่อเมื่อหลุดออกจากระดับบนสุดของโครูทีนลูก กล่าวคือไม่มี try-catch อยู่ในโครูทีนลูก ส่วนขอบเขตแม่จะไม่เห็นข้อยกเว้นนั้น

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        // Exception caught inside child — scope unaffected
        launch {
            try { throw RuntimeException("handled") }
            catch (e: Exception) { println("Child caught: ${e.message}") }
        }
        delay(100)
        println("Scope survived")
    }
}

CoroutineExceptionHandler กับ SupervisorJob

ติดตั้ง CoroutineExceptionHandler ในขอบเขตแบบผู้ควบคุม เพื่อบันทึกหรือจัดการกับความล้มเหลวของโครูทีนลูกที่ไม่มีการจัดการ โดยไม่ทำให้ขอบเขตหยุดทำงาน

import kotlinx.coroutines.*
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Uncaught: ${e.message}")
    }
    val scope = CoroutineScope(SupervisorJob() + handler)
    scope.launch { throw RuntimeException("unhandled failure") }
    scope.launch { delay(100); println("still running") }
    delay(200)
    scope.cancel()
}

สรุปการแยกความล้มเหลว

Job: โครูทีนลูกหนึ่งล้มเหลว → ทั้งหมดล้มเหลว SupervisorJob: โครูทีนลูกล้มเหลวแยกจากกัน ใช้ supervisor สำหรับบริการและ ViewModels และใช้ Job ปกติสำหรับกลุ่มแบบธุรกรรม

import kotlinx.coroutines.*
// Decision table:
// coroutineScope { }   -> Job: all-or-nothing
// supervisorScope { }  -> SupervisorJob: independent failures
// CoroutineScope(SupervisorJob()) -> long-lived service
fun main() = runBlocking { println("Pick the right job for the right scope") }

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ขอบเขตใดป้องกันไม่ให้ความล้มเหลวของโครูทีนลูกยกเลิกโครูทีนพี่น้อง

ทบทวน

Job: ความล้มเหลวส่งผลต่อโครูทีนพี่น้อง SupervisorJob/supervisorScope: แยกความล้มเหลวของโครูทีนลูกแต่ละตัว ใช้ supervisorScope สำหรับงานแบบขนานที่เป็นอิสระต่อกัน และใช้ coroutineScope ปกติสำหรับกลุ่มแบบธุรกรรม

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

บทเรียน “SupervisorJob กับ Job: การแยกความล้มเหลว” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “SupervisorJob กับ Job: การแยกความล้มเหลว” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “SupervisorJob กับ Job: การแยกความล้มเหลว”

ใช้ SupervisorJob เพื่อป้องกันไม่ให้ความล้มเหลวของ coroutine ลูกหนึ่งยกเลิกพี่น้อง คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “SupervisorJob กับ Job: การแยกความล้มเหลว” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม

ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. SupervisorJob กับ Job: การแยกความล้มเหลว
  2. CoroutineExceptionHandler: ตัวจัดการข้อผิดพลาดที่ไม่ถูกดักจับระดับ Global
  3. การส่งต่อ Exception ของ async/await
  4. การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน
← กลับไปที่ Kotlin Academy