Suspend function คือฟังก์ชันที่มีตัวปรับ suspend ซึ่งสามารถหยุดการทำงานชั่วคราวโดยไม่บล็อกเธรดและกลับมาเริ่มใหม่ได้ในโครูทีนเดียวกัน ตาม JetBrains Kotlin Docs, 2025 ฟังก์ชัน suspend เป็นส่วนประกอบพื้นฐานของโครูทีนที่ให้ความไม่พร้อมกันโดยไม่ต้องใช้ callback ฟังก์ชัน suspend แต่ละฟังก์ชันถูกคอมไพล์เป็นเครื่องสถานะบนพื้นฐานของ Continuation ซึ่งช่วยให้จัดการจุดหยุดชั่วคราวได้อย่างมีประสิทธิภาพ
ประเด็นสำคัญ
Suspend function คือฟังก์ชันที่ประกาศด้วยคำสำคัญ suspend ซึ่งสามารถหยุดการทำงานชั่วคราวที่จุดหนึ่งจุดหรือมากกว่าโดยไม่บล็อกเธรด ทุกครั้งที่เรียกฟังก์ชัน suspend ภายในฟังก์ชัน suspend อื่นคือจุดหยุดชั่วคราวที่อาจเกิดขึ้น
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
คอมไพเลอร์ Kotlin แปลงฟังก์ชันดังกล่าวเป็นเครื่องสถานะ จุดหยุดชั่วคราวแต่ละจุด (การเรียกฟังก์ชัน suspend อื่น) กลายเป็นสถานะ (label) เธรดปัจจุบันถูกปล่อยระหว่างสถานะ และหลังจากการดำเนินการที่รอคอยเสร็จสมบูรณ์ การทำงานจะกลับมาทำงานต่อจากสถานะถัดไป
ฟังก์ชัน suspend ปรากฏใน Kotlin 1.3 (2018) พร้อมกับโครูทีนในฐานะคุณสมบัติทดลองและกลายเป็นเสถียรใน Kotlin 1.5 (2021) ก่อนหน้านั้น ความไม่พร้อมกันใน Kotlin/Java ทำได้ผ่าน callback, RxJava และ CompletableFuture ฟังก์ชัน suspend เสนอทางเลือกที่มีไวยากรณ์เชิงเส้นและการจัดการเธรดอัตโนมัติ
การเข้าใจการทำงานภายในของฟังก์ชัน suspend คือกุญแจสำคัญในการทำงานกับโครูทีนอย่างถูกต้อง ซึ่งแตกต่างจากฟังก์ชันปกติ ฟังก์ชัน suspend แต่ละฟังก์ชันถูกคอมไพล์เป็นคลาสที่มีอินเทอร์เฟซ Continuation
คอมไพเลอร์ Kotlin เพิ่มพารามิเตอร์ชนิด Continuation ที่ท้ายรายการพารามิเตอร์ของฟังก์ชัน suspend แต่ละฟังก์ชัน Continuation ประกอบด้วย:
สมมติว่าเรามีฟังก์ชัน suspend ที่มีการเรียกฟังก์ชัน suspend อื่นสองครั้ง:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
คอมไพเลอร์แปลงเป็นเครื่องสถานะที่มีป้ายกำกับ:
// Simplified generated code representation
fun process(cont: Continuation<Unit>): Any? {
val cont = cont as ProcessContinuation
when (cont.label) {
0 -> {
cont.label = 1
if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
1 -> {
cont.label = 2
val a = cont.result as TypeA
if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
2 -> {
println(cont.result)
Unit
}
}
}
ข้อสังเกตสำคัญ: ถ้าฟังก์ชันคืนค่า COROUTINE_SUSPENDED เธรดปัจจุบันจะถูกปล่อย เมื่อการดำเนินการแบบไม่พร้อมกันเสร็จสมบูรณ์ Continuation.resumeWith จะถูกเรียกและเครื่องสถานะดำเนินการต่อจากป้ายกำกับถัดไป
การประกาศฟังก์ชัน suspend ไม่แตกต่างจากฟังก์ชันปกติ ยกเว้นคำสำคัญ suspend ก่อน fun มีข้อจำกัดเพียงข้อเดียว: ฟังก์ชัน suspend สามารถเรียกได้จากโครูทีนหรือฟังก์ชัน suspend อื่นเท่านั้น
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
ในตัวอย่างนี้ delay ก็เป็นฟังก์ชัน suspend ที่หยุดโครูทีนตามจำนวนมิลลิวินาทีที่ระบุโดยไม่บล็อกเธรด หลังจากหน่วงเวลา การทำงานจะกลับมาดำเนินการต่อ
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlocking สร้างสะพานเชื่อมระหว่างโลกปกติและโครูทีน ภายใน lambda สามารถเรียกฟังก์ชัน suspend ใด ๆ ได้
Kotlin รองรับเวอร์ชัน suspend ของชนิดฟังก์ชัน — suspend () -> T และ suspend (A) -> B ซึ่งอนุญาตให้ส่ง lambda แบบไม่พร้อมกันไปยังฟังก์ชันลำดับสูง:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
ฟังก์ชัน withRetry รับ lambda แบบ suspend และลองดำเนินการใหม่เมื่อเกิดข้อผิดพลาด นี่เป็นรูปแบบทั่วไปสำหรับคำขอเครือข่ายที่มีการลองใหม่
ความแตกต่างระหว่างฟังก์ชัน suspend และฟังก์ชันปกติไม่ได้มีเพียงแค่การเพิ่มตัวปรับ มาดูความแตกต่างหลักกัน
| ลักษณะ | ฟังก์ชันปกติ | ฟังก์ชัน suspend |
|---|---|---|
| เธรดการทำงาน | บล็อกเธรดจนกว่าจะเสร็จ | สามารถปล่อยเธรดและกลับมาทำงานต่อได้ |
| พารามิเตอร์ของคอมไพเลอร์ | เฉพาะพารามิเตอร์ที่ระบุ | Continuation โดยนัยที่ท้าย |
| เรียกจากฟังก์ชันปกติ | ได้ | ไม่ได้ |
| สแต็ก | สแต็กทางกายภาพของเธรด | เครื่องสถานะในฮีป + สแต็กทางกายภาพระหว่างจุด |
| ค่าที่คืน | ค่าโดยตรง | ค่าหรือ COROUTINE_SUSPENDED |
| ประสิทธิภาพ | ค่าใช้จ่ายน้อยที่สุด | ~ไม่กี่นาโนวินาทีต่อเครื่องสถานะ (Kotlin 1.9+) |
ฟังก์ชันปกติไม่มี Continuation — ไม่มีที่สำหรับบันทึกสถานะและไม่มีอะไรสำหรับกลับมาทำงานต่อ หากคุณต้องการเรียกฟังก์ชัน suspend จากฟังก์ชันปกติ ให้ใช้ runBlocking (สำหรับการทดสอบ) หรือ CoroutineScope.launch (สำหรับการผลิตโดยคำนึงถึงวงจรชีวิต)
มาดูสามสถานการณ์จริงของการใช้ฟังก์ชัน suspend ในแอป Android ด้วย Kotlin กัน
Room รองรับฟังก์ชัน suspend โดยตรง — คำสั่งถูกดำเนินการบนเธรดพื้นหลังโดยอัตโนมัติ:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Room ใช้ Dispatchers.IO ภายในเพื่อดำเนินการคำสั่ง และผลลัพธ์จะถูกส่งคืนบนตัวจัดส่งที่เรียกฟังก์ชัน suspend
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launch สร้างโครูทีน ซึ่งภายในเรียกฟังก์ชัน suspend getProfile หลังจากได้รับผลลัพธ์ ส่วนติดต่อผู้ใช้จะถูกอัปเดตบนเธรดหลัก
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
ฟังก์ชัน suspend สามฟังก์ชันทำงานตามลำดับ ในแต่ละขั้นตอน โครูทีนสามารถหยุดชั่วคราวได้โดยไม่บล็อกเธรด ถ้าขั้นตอนใดเกิดข้อยกเว้น ขั้นตอนที่เหลือจะไม่ทำงาน ป้องกันสถานะคำสั่งซื้อที่ไม่ถูกต้อง
แม้แต่นักพัฒนา Kotlin ที่มีประสบการณ์ก็ยังทำผิดพลาดเมื่อออกแบบฟังก์ชัน suspend มาดูข้อผิดพลาดที่พบบ่อยที่สุดกัน
ฟังก์ชัน suspend ไม่ได้ทำให้โค้ดไม่พร้อมกันโดยอัตโนมัติ Thread.sleep(), InputStream.read() และการเรียกแบบบล็อกอื่น ๆ ยังคงบล็อกเธรด ใช้ withContext(Dispatchers.IO) เพื่อครอบคลุมการดำเนินการแบบบล็อก
ถ้าฟังก์ชันไม่เรียกฟังก์ชัน suspend อื่นและไม่ดำเนินการแบบไม่พร้อมกัน — ตัวปรับ suspend นั้นซ้ำซ้อน มันเพิ่มค่าใช้จ่ายให้กับเครื่องสถานะและจำกัดบริบทการเรียก ทำให้ฟังก์ชันเป็น suspend เฉพาะเมื่อมันหยุดชั่วคราวจริง ๆ
เมื่อโครูทีนถูกยกเลิก ฟังก์ชัน suspend จะโยน CancellationException อย่าจับมันโดยไม่คิด — คุณกำลังทำให้โค้ดที่เรียกสูญเสียความสามารถในการดำเนินการยกเลิกอย่างถูกต้อง ถ้าคุณต้องการดำเนินการสิ้นสุด ให้ใช้บล็อก finally และ NonCancellable
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
บล็อก finally จะทำงานเสมอ รวมถึงเมื่อยกเลิก และ NonCancellable รับประกันว่าการทำความสะอาดจะไม่ถูกขัดจังหวะ
คุณไม่สามารถเรียกฟังก์ชัน suspend โดยตรงจาก callback โดยไม่สร้างโครูทีน ใช้ suspendCoroutine หรือ suspendCancellableCoroutine เพื่อปรับสไตล์ callback ให้เข้ากับโครูทีน
คำถามที่พบบ่อย
ได้ ในทางเทคนิคฟังก์ชัน suspend สามารถไม่เรียกฟังก์ชัน suspend อื่นได้ คอมไพเลอร์จะสร้างเครื่องสถานะที่มีสถานะเดียว (label 0) อย่างไรก็ตาม ไม่มีประโยชน์ในทางปฏิบัติสำหรับฟังก์ชันดังกล่าว — มันทำงานเหมือนฟังก์ชันปกติแต่มีค่าใช้จ่ายเพิ่ม อย่าใช้ suspend โดยไม่จำเป็น
Kotlin มี kotlinx-coroutines-debug — ไลบรารีที่มี DebugProbes และเครื่องมือติดตามโครูทีน ใน Android Studio ตั้งแต่ Arctic Fox มีแท็บ Coroutines ในตัวในดีบักเกอร์ที่แสดงโครูทีนที่ทำงานอยู่ สถานะ และจุดหยุดชั่วคราว
แต่ละจุดหยุดชั่วคราวสร้างสถานะใหม่ในเครื่องสถานะ สำหรับแอปพลิเคชันส่วนใหญ่ ค่าใช้จ่ายของหนึ่งจุดคือไม่กี่นาโนวินาที (Kotlin 1.9+) เฉพาะเมื่อมีจุดนับหมื่นในลูป คุณควรพิจารณารวมการดำเนินการหรือใช้ sequence/flow
ใน Kotlin suspend เป็นตัวปรับชนิดฟังก์ชัน ไม่ใช่ตัวบอกค่าที่คืน (เช่น async ใน C#) ฟังก์ชัน suspend ใด ๆ สามารถมีพารามิเตอร์และชนิดที่คืนใด ๆ และการเรียกของมันในทางไวยากรณ์ไม่แตกต่างจากการเรียกฟังก์ชันปกติ — ไม่มีตัวดำเนินการ await ที่ตำแหน่งที่เรียก
ใช้ suspendCancellableCoroutine สำหรับการปรับเปลี่ยน ภายใน คุณลงทะเบียน callback ที่เรียก continuation.resume() และคืนโทเคนการยกเลิกถ้า callback รองรับการยกเลิกการสมัครสมาชิก นี่เป็นรูปแบบมาตรฐานสำหรับครอบคลุม Android API เก่า
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม