Kotlin Academy · บทเรียน

การส่งต่อการยกเลิกในลำดับชั้น Coroutine

ทำความเข้าใจว่าการยกเลิกไหลผ่านความสัมพันธ์ระหว่าง coroutine แม่และลูกอย่างไร

บทเรียน 4 จาก 413 ขั้นตอน

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

ความสัมพันธ์ระหว่างแม่กับลูก

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

import kotlinx.coroutines.*
fun main() = runBlocking {
    val parent = launch {
        launch { delay(1000); println("child 1") }
        launch { delay(1000); println("child 2") }
        delay(1000)
        println("parent")
    }
    delay(50)
    parent.cancel()
    parent.join()
    println("All cancelled")
}

ความล้มเหลวของลูกทำให้แม่ถูกยกเลิก

หากลูกโยนข้อยกเว้นที่ไม่ใช่การยกเลิก ลูกจะยกเลิกแม่และพี่น้องทั้งหมด นี่คือพฤติกรรมเริ่มต้นของ Job

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

การยกเลิกไม่ส่งต่อขึ้นไปยังแม่

การยกเลิกลูกจะไม่ยกเลิกแม่ มีเพียงข้อยกเว้นที่ไม่ได้จัดการเท่านั้นที่ทำเช่นนั้นได้ ส่วนแม่สามารถยกเลิกลูกได้ตามต้องการ

import kotlinx.coroutines.*
fun main() = runBlocking {
    val parent = launch {
        val child = launch {
            delay(1000)
            println("child done")
        }
        delay(50)
        child.cancel()  // cancels child
        child.join()
        println("parent still running")  // parent is fine
    }
    parent.join()
}

coroutineScope เทียบกับ GlobalScope

coroutineScope จะสร้างขอบเขตลูก ซึ่งการยกเลิกและข้อผิดพลาดจะแพร่ต่อไป ส่วน GlobalScope จะสร้างโคโรทีนไร้เจ้าของที่ไม่มีแม่ ควรหลีกเลี่ยงในการใช้งานจริง

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            launch { delay(50); throw RuntimeException("error") }
        }
    } catch (e: Exception) {
        println("coroutineScope propagated: ${e.message}")
    }
    // GlobalScope.launch { } would NOT propagate to runBlocking
}

การยกเลิกส่วนย่อยของลำดับชั้น

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

import kotlinx.coroutines.*
fun main() = runBlocking {
    val root = launch {
        launch {
            launch { delay(1000); println("deep") }
            delay(1000)
        }
    }
    delay(50)
    root.cancel()
    root.join()
    println("Whole subtree cancelled")
}

join() หลังจาก cancel()

ให้เรียก join() หลังจาก cancel() เสมอ เพื่อรอให้โคโรทีนและลูกของมันหยุดทำงานอย่างสมบูรณ์ก่อนดำเนินการต่อ

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { println("cleanup ran") }
    }
    delay(50)
    job.cancel()
    job.join()  // waits for finally to complete
    println("Proceeded after join")
}

cancelAndJoin()

job.cancelAndJoin() เป็นฟังก์ชันอำนวยความสะดวกที่ยกเลิกแล้วจึงเข้าร่วมงาน ซึ่งเทียบเท่ากับ cancel() + join()

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { println("cleanup") }
    }
    delay(50)
    job.cancelAndJoin()  // cancel + join in one call
    println("Done")
}

การแพร่ผ่าน async

เมื่อใช้ async ข้อยกเว้นจะถูกเก็บไว้ใน Deferred และจะถูกโยนเมื่อเรียก await() ข้อยกเว้นเหล่านั้นยังคงแพร่ไปยังแม่หากไม่ได้จัดการ

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            val d = async { throw RuntimeException("async fail") }
            d.await()  // rethrows here
        }
    } catch (e: RuntimeException) {
        println("Caught from async: ${e.message}")
    }
}

หลักประกันของการทำงานพร้อมกันแบบมีโครงสร้าง

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

import kotlinx.coroutines.*
suspend fun doWork() = coroutineScope {
    launch { delay(100); println("work 1") }
    launch { delay(200); println("work 2") }
    // both children complete before doWork returns
}
fun main() = runBlocking {
    doWork()
    println("All work done")
}

แผนภาพการแพร่ของการยกเลิก

ลำดับชั้นมีดังนี้: ยกเลิกแม่ → ลูกทั้งหมดถูกยกเลิก ยกเลิกลูก → ยกเลิกเฉพาะส่วนย่อยนั้น ข้อยกเว้นของลูก → แม่ถูกยกเลิก → พี่น้องถูกยกเลิก

import kotlinx.coroutines.*
fun main() = runBlocking {
    // Parent
    launch {
        val c1 = launch { delay(1000); println("c1") }  // child 1
        val c2 = launch { delay(1000); println("c2") }  // child 2
        delay(50)
        c1.cancel()  // only c1 cancelled
        c1.join()
        println("c2 still active: ${c2.isActive}")
        c2.cancelAndJoin()
    }.join()
}

วงจรชีวิตของ CoroutineScope ในแอนดรอยด์

ในแอนดรอยด์ viewModelScope จะถูกยกเลิกเมื่อ ViewModel ถูกล้างข้อมูล โคโรทีนทั้งหมดที่เปิดใช้งานจะถูกยกเลิกโดยอัตโนมัติ จึงไม่ต้องล้างข้อมูลด้วยตนเอง

import kotlinx.coroutines.*
// Pseudocode:
// class MyViewModel : ViewModel() {
//     fun load() = viewModelScope.launch {
//         val data = repo.fetch() // cancelled if VM cleared
//         _state.value = data
//     }
// }
fun main() = runBlocking { println("viewModelScope cancels on ViewModel.onCleared()") }

ตรวจสอบอย่างรวดเร็ว

โคโรทีนพี่น้องจะเป็นอย่างไรเมื่อโคโรทีนลูกโยนข้อยกเว้นที่ไม่ได้จัดการภายใต้ Job ปกติ

ทบทวน

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

เริ่มต้นได้ฟรี

เรียนรู้ Kotlin ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
51
บทเรียน
203

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

บทเรียน “การส่งต่อการยกเลิกในลำดับชั้น Coroutine” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “การส่งต่อการยกเลิกในลำดับชั้น Coroutine”

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

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

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

บทเรียน “การส่งต่อการยกเลิกในลำดับชั้น Coroutine” ใช้เวลานานแค่ไหน

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

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

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

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

  1. การยกเลิกร่วมมือกัน: isActive และ ensureActive
  2. withTimeout และ withTimeoutOrNull
  3. การเก็บกวาดด้วย finally และ NonCancellable
  4. การส่งต่อการยกเลิกในลำดับชั้น Coroutine
← กลับไปที่ Kotlin Academy