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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- SupervisorJob กับ Job: การแยกความล้มเหลว
- CoroutineExceptionHandler: ตัวจัดการข้อผิดพลาดที่ไม่ถูกดักจับระดับ Global
- การส่งต่อ Exception ของ async/await
- การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน