CoroutineScope เป็นอินเทอร์เฟส Kotlin ที่กำหนดวงจรชีวิตของคอรูนและให้บริบทสำหรับเริ่มต้นคอรูนใหม่ ตาม เอกสารของ Kotlin, 2025 แต่ละอินสแตนซ์ของ CoroutineScope มี CoroutineContext และจัดการคอรูนทั้งหมดที่เริ่มต้นภายใน เมื่อ scope ถูกยกเลิก (cancel) คอรูนลูกทั้งหมดจะถูกยกเลิกโดยอัตโนมัติ ป้องกันหน่วยความจำรั่ว
ประเด็นสำคัญ
CoroutineScope เป็นอินเทอร์เฟสพื้นฐานจากไลบรารี kotlinx.coroutines ที่ทำหน้าที่เป็นคอนเทนเนอร์สำหรับคอรูน กำหนดขอบเขตของวงจรชีวิตคอรูน: เมื่อ scope เสร็จสมบูรณ์ คอรูนทั้งหมดภายในจะถูกยกเลิกโดยอัตโนมัติ
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
อินเทอร์เฟสมีเพียงฟิลด์เดียว — coroutineContext ผ่านฟิลด์นี้ scope จะจัดหาตัวจัดสรร (Dispatcher) งาน (Job) ตัวจัดการข้อยกเว้น และองค์ประกอบบริบทอื่น ๆ ให้กับคอรูนทั้งหมดที่เริ่มต้นภายใน
ฟังก์ชันเริ่มต้นคอรูนทั้งหมด — launch, async, runBlocking — เป็นฟังก์ชันส่วนขยายบน CoroutineScope ซึ่งหมายความว่าสามารถเรียกได้เมื่อมีวัตถุ scope เท่านั้น การออกแบบนี้รับประกันว่าคอรูนแต่ละตัวมีแม่และวงจรชีวิตที่กำหนดไว้อย่างชัดเจน
ใน Android แต่ละคอมโพเนนต์เชิงสถาปัตยกรรมมี scope ของตัวเอง: viewModelScope สำหรับ ViewModel, lifecycleScope สำหรับ Activity/Fragment ในแอปพลิเคชันเซิร์ฟเวอร์ scope สามารถผูกกับคำขอ HTTP หรือพูลการเชื่อมต่อฐานข้อมูล
การทำความเข้าใจการทำงานภายในของ CoroutineScope ต้องคุ้นเคยกับแนวคิดของ Job และหลักการของ การทำงานพร้อมกันแบบมีโครงสร้าง
คอรูนแต่ละตัวเมื่อเริ่มต้นจะส่งคืนวัตถุ Job (หรือ Deferred สำหรับ async) Job แทนงานที่มีวงจรชีวิตจำกัด: New, Active, Completing, Completed, Cancelling, Cancelled วัตถุ Job สร้างโครงสร้างแบบต้นไม้:
การทำงานพร้อมกันแบบมีโครงสร้าง เป็นหลักการเชิงสถาปัตยกรรมหลักของ Kotlin Coroutines ที่วงจรชีวิตของคอรูนผูกกับวงจรชีวิตของ scope ซึ่งตรงกันข้ามกับโมเดล “ยิงแล้วลืม” ที่คอรูนยังคงมีชีวิตอยู่หลังจาก scope เสร็จสมบูรณ์ ข้อดีของการทำงานพร้อมกันแบบมีโครงสร้าง:
เมื่อเรียก scope.cancel() Job ของ scope จะเปลี่ยนเป็นสถานะ Cancelled ซึ่งจะยกเลิก Job ลูกทั้งหมดแบบเรียกซ้ำ หลังจากการยกเลิก scope สามารถนำกลับมาใช้ใหม่ได้ก็ต่อเมื่อสร้างอินสแตนซ์ CoroutineScope ใหม่
คุณสามารถสร้าง CoroutineScope ผ่านฟังก์ชันโรงงานหรือโดยการใช้อินเทอร์เฟสในคลาสของคุณ มาดูทั้งสองวิธี
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Running on ${Thread.currentThread().name}")
}
ฟังก์ชันโรงงานรับ CoroutineContext และสร้าง scope ด้วยบริบทที่ระบุ ตัวอย่างใช้ Dispatchers.Default สำหรับงานที่ใช้ CPU มาก และ SupervisorJob ซึ่งแยกข้อยกเว้นระหว่างคอรูนลูก
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
เราเก็บ scope เป็นฟิลด์ของคลาสและเรียก cleanup ด้วยตนเองเพื่อยกเลิก วิธีนี้เหมาะสำหรับคอมโพเนนต์ที่มีวงจรชีวิตที่จัดการได้ — เช่น repository หรือ manager
Kotlin อนุญาตให้มอบหมายการใช้งาน CoroutineScope ผ่านคีย์เวิร์ด by:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// coroutine runs in DataLoader scope
}
}
}
วิธีนี้สะดวกเมื่อคลาสเองเป็น scope และต้องการให้เมธอดเริ่มต้นคอรูน อย่างไรก็ตาม ระวัง: คลาสสืบทอดเมธอดทั้งหมดของ CoroutineScope รวมถึง cancel ซึ่งอาจทำลายการห่อหุ้ม
GlobalScope เป็น CoroutineScope แบบซิงเกิลตันสำหรับทั้งแอปพลิเคชัน การใช้งานในโค้ดโปรดักชันไม่ได้รับการแนะนำอย่างเป็นทางการ
JetBrains อนุญาต GlobalScope เฉพาะในสถานการณ์ที่หายาก: กระบวนการพื้นหลังระดับแอปพลิเคชัน ที่ควรมีชีวิตอยู่แม้หลังจากปิด Activity ทั้งหมดแล้ว (เช่น การซิงค์ข้อมูล การวิเคราะห์) แต่แม้ในกรณีเหล่านี้ ควรสร้าง scope ของคุณเองด้วย CoroutineScope(SupervisorJob())
ใช้ CoroutineScope ที่กำหนดเอง เสมอพร้อมการจัดการวงจรชีวิตที่ชัดเจน ใน Android คือ viewModelScope และ lifecycleScope ในแอปพลิเคชันเซิร์ฟเวอร์ ให้สร้าง scope สำหรับแต่ละคำขอหรือพูลการเชื่อมต่อ
ทั้งสองฟังก์ชันเป็นฟังก์ชันระงับที่สร้าง scope ชั่วคราวสำหรับงานแบบขนาน แต่พฤติกรรมเมื่อมีข้อยกเว้นแตกต่างกันโดยพื้นฐาน
| ลักษณะ | coroutineScope | supervisorScope |
|---|---|---|
| พฤติกรรมเมื่อเกิดข้อผิดพลาด | ข้อยกเว้นในคอรูนลูกยกเลิกตัวอื่นทั้งหมด | ข้อยกเว้นในคอรูนลูกไม่ยกเลิกตัวอื่น |
| การแพร่กระจายข้อผิดพลาด | ใช่ ข้อยกเว้นแรกแพร่กระจายออกไปด้านนอก | ใช่ ข้อยกเว้นแรกแพร่กระจายออกไปด้านนอก |
| Job เริ่มต้น | Job() — ลูกผูกกับแม่ | SupervisorJob() — ลูกไม่พึ่งพากัน |
| กรณีการใช้งานทั่วไป | การดำเนินการอะตอมมิกหลายขั้นตอน | งานขนานอิสระ (การโหลด UI) |
ใช้ coroutineScope เมื่อการดำเนินการแบบขนานหลายรายการก่อให้เกิดการดำเนินการอะตอมมิกเดียว ตัวอย่างเช่น การโหลดข้อมูลจากสามเซิร์ฟเวอร์: หากคำขอหนึ่งล้มเหลว คำขอที่เหลือก็ไร้ความหมาย
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
หาก getProduct หรือ getReviews โยนข้อยกเว้น — คอรูนทั้งสองจะถูกยกเลิก และข้อยกเว้นแพร่กระจายไปยังโค้ดที่เรียก
ใช้ supervisorScope เมื่อการดำเนินการแบบขนานไม่พึ่งพากัน ตัวอย่างเช่น การโหลดข้อมูลโปรไฟล์ในหลายส่วนอิสระ: หากส่วนคำแนะนำล้มเหลว ส่วนหัวโปรไฟล์และรายการเพื่อนควรแสดงผล
มาดูข้อผิดพลาดที่พบบ่อยที่สุดของนักพัฒนาเมื่อใช้ CoroutineScope ใน Kotlin
สถานการณ์การรั่วไหลของคอรูนที่พบบ่อยที่สุดคือการสร้าง scope โดยไม่เรียก cancel เมื่อคอมโพเนนต์สิ้นสุด หาก scope ไม่ถูกยกเลิก คอรูนจะยังคงทำงานต่อไป โดยเก็บการอ้างอิงถึงวัตถุ ใน Android ให้ใช้ viewModelScope หรือ lifecycleScope ซึ่งจะถูกยกเลิกโดยอัตโนมัติ
GlobalScope ไม่สนใจวงจรชีวิตของคอมโพเนนต์ Android คอรูนที่เริ่มต้นใน GlobalScope หลังจากปิด Activity จะยังคงทำงานและพยายามอัปเดต UI — ซึ่งจะทำให้เกิดการขัดข้อง ใช้ lifecycleScope สำหรับคอมโพเนนต์ UI เสมอ
หลังจากเรียก cancel() แล้ว scope ไม่สามารถนำกลับมาใช้ใหม่ได้ — คอรูนทั้งหมดภายในเสร็จสมบูรณ์แล้ว สร้างอินสแตนซ์ CoroutineScope ใหม่ผ่านฟังก์ชันโรงงาน Job() ไม่สนับสนุนการเปิดใช้งานอีกครั้ง
เมื่อมอบหมายด้วย by คลาสจะได้รับเมธอด cancel() สาธารณะที่สามารถเรียกได้จากทุกที่ ทำลายการห่อหุ้ม เก็บ scope เป็นฟิลด์ส่วนตัวแทนที่จะมอบหมายอินเทอร์เฟส
คำถามที่พบบ่อย
CoroutineScope เป็นอินเทอร์เฟสที่เป็นเจ้าของ CoroutineContext และรับผิดชอบวงจรชีวิตของคอรูน CoroutineContext คือชุดขององค์ประกอบ (ตัวจัดสรร job ตัวจัดการข้อผิดพลาด) ที่กำหนดว่าคอรูนทำงาน “อย่างไร” ความแตกต่างหนึ่ง: scope สร้างคอรูน ในขณะที่บริบทควบคุมพฤติกรรมของพวกมัน
ได้ มันเป็นรูปแบบมาตรฐาน: CoroutineScope(Dispatchers.IO + SupervisorJob()) SupervisorJob ป้องกันการยกเลิกลูกโซ่ของคอรูนลูกเมื่อหนึ่งในนั้นโยนข้อยกเว้น มีประโยชน์สำหรับงานขนานอิสระที่ข้อผิดพลาดในหนึ่งไม่ควรหยุดตัวอื่น
ไม่มีข้อจำกัดเกี่ยวกับจำนวนคอรูนใน scope — จำกัดเฉพาะหน่วยความจำที่มีอยู่และการตั้งค่าตัวจัดสรรเท่านั้น ขีดจำกัดในทางปฏิบัติโดยทั่วไปคือ คอรูนที่ทำงานอยู่หลายพันตัวใน scope เดียว อย่างไรก็ตาม จำนวนคอรูนที่มากอาจบ่งบอกถึงปัญหาเชิงสถาปัตยกรรม
วิธีที่ถูกต้องคือการส่ง scope ไปยังคลาสผ่านตัวสร้างหรือใช้ runBlockingTest / runTest จาก kotlinx-coroutines-test ในการทดสอบ คุณสามารถแทนที่ scope ด้วย TestCoroutineDispatcher และควบคุมการทำงานของคอรูนด้วยตนเอง
ไม่ scope เป็นคอนเทนเนอร์ภายนอกสำหรับคอรูน คอรูนเองไม่ใช่ scope อย่างไรก็ตาม ภายในคอรูนคุณสามารถสร้าง scope ใหม่ผ่าน coroutineScope หรือ supervisorScope เพื่อเริ่มต้นคอรูนลูกแบบขนาน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม