Suspend function là hàm có bổ từ suspend có thể tạm dừng thực thi mà không chặn luồng và tiếp tục sau đó trong cùng một coroutine. Theo JetBrains Kotlin Docs, 2025, các hàm suspend là khối xây dựng cơ bản của coroutine, cung cấp khả năng bất đồng bộ mà không cần callback. Mỗi hàm suspend được biên dịch thành một máy trạng thái dựa trên Continuation, cho phép quản lý hiệu quả các điểm tạm dừng.
Những điểm chính
Suspend function là hàm được khai báo với từ khóa suspend có thể tạm dừng thực thi tại một hoặc nhiều điểm mà không chặn luồng. Mỗi lần gọi hàm suspend bên trong một hàm suspend khác là một điểm tạm dừng tiềm năng.
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
Trình biên dịch Kotlin dịch hàm như vậy thành một máy trạng thái. Mỗi điểm tạm dừng (một lần gọi hàm suspend khác) trở thành một trạng thái (label). Luồng hiện tại được giải phóng giữa các trạng thái và sau khi hoạt động đang chờ hoàn thành, việc thực thi tiếp tục từ trạng thái tiếp theo.
Các hàm suspend xuất hiện trong Kotlin 1.3 (2018) cùng với coroutine như một tính năng thử nghiệm và trở nên ổn định trong Kotlin 1.5 (2021). Trước đó, tính bất đồng bộ trong Kotlin/Java được thực hiện thông qua callback, RxJava và CompletableFuture. Các hàm suspend đã cung cấp một giải pháp thay thế với cú pháp tuyến tính và quản lý luồng tự động.
Hiểu hoạt động bên trong của các hàm suspend là chìa khóa để làm việc chính xác với coroutine. Không giống như các hàm thông thường, mỗi hàm suspend được biên dịch thành một lớp với giao diện Continuation.
Trình biên dịch Kotlin thêm một tham số kiểu Continuation vào cuối mỗi danh sách tham số của hàm suspend. Continuation chứa:
Giả sử chúng ta có một hàm suspend với hai lần gọi đến các hàm suspend khác:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
Trình biên dịch biến đổi nó thành một máy trạng thái với các nhãn:
// 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
}
}
}
Quan sát chính: nếu hàm trả về COROUTINE_SUSPENDED, luồng hiện tại được giải phóng. Khi hoạt động bất đồng bộ hoàn thành, Continuation.resumeWith được gọi và máy trạng thái tiếp tục từ nhãn tiếp theo.
Khai báo hàm suspend không khác gì hàm thông thường, ngoại trừ từ khóa suspend trước fun. Chỉ có một hạn chế: hàm suspend chỉ có thể được gọi từ coroutine hoặc từ hàm suspend khác.
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
Trong ví dụ này, delay cũng là một hàm suspend, tạm dừng coroutine trong số mili giây được chỉ định mà không chặn luồng. Sau khi trì hoãn, việc thực thi tiếp tục.
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlocking tạo một cầu nối giữa thế giới thông thường và coroutine. Bên trong lambda, bất kỳ hàm suspend nào cũng có thể được gọi.
Kotlin hỗ trợ các phiên bản suspend của kiểu hàm — suspend () -> T và suspend (A) -> B. Điều này cho phép truyền lambda bất đồng bộ vào các hàm bậc cao:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
Hàm withRetry nhận một lambda suspend và thử lại thực thi khi có lỗi. Đây là mẫu điển hình cho các yêu cầu mạng có thử lại.
Sự khác biệt giữa hàm suspend và hàm thông thường không chỉ đơn giản là thêm một bổ từ. Hãy xem xét những điểm khác biệt chính.
| Đặc điểm | Hàm thông thường | Hàm suspend |
|---|---|---|
| Luồng thực thi | Chặn luồng cho đến khi hoàn thành | Có thể giải phóng luồng và tiếp tục sau |
| Tham số trình biên dịch | Chỉ tham số được chỉ định | Continuation ngầm ở cuối |
| Gọi từ hàm thông thường | Có | Không |
| Ngăn xếp | Ngăn xếp vật lý của luồng | Máy trạng thái trong heap + ngăn xếp vật lý giữa các điểm |
| Giá trị trả về | Giá trị trực tiếp | Giá trị hoặc COROUTINE_SUSPENDED |
| Hiệu suất | Chi phí tối thiểu | ~vài nano giây cho mỗi máy trạng thái (Kotlin 1.9+) |
Hàm thông thường không có Continuation — nó không có nơi nào để lưu trạng thái và không có gì để tiếp tục thực thi. Nếu bạn cần gọi hàm suspend từ hàm thông thường, hãy sử dụng runBlocking (cho kiểm thử) hoặc CoroutineScope.launch (cho sản xuất có tính đến vòng đời).
Hãy xem ba kịch bản thực tế sử dụng hàm suspend trong ứng dụng Android với Kotlin.
Room hỗ trợ trực tiếp hàm suspend — truy vấn được thực thi tự động trên luồng nền:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Room nội bộ sử dụng Dispatchers.IO để thực thi truy vấn và kết quả được trả về trên bộ điều phối nơi hàm suspend được gọi.
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launch tạo một coroutine, bên trong đó hàm suspend getProfile được gọi. Sau khi nhận được kết quả, giao diện người dùng được cập nhật trên luồng chính.
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
Ba hàm suspend thực thi tuần tự. Tại mỗi bước, coroutine có thể tạm dừng mà không chặn luồng. Nếu bước nào ném ra ngoại lệ, các bước còn lại không thực thi, bảo vệ khỏi các trạng thái đơn hàng không chính xác.
Ngay cả các nhà phát triển Kotlin giàu kinh nghiệm cũng mắc lỗi khi thiết kế hàm suspend. Hãy xem những lỗi phổ biến nhất.
Hàm suspend không tự động làm cho mã trở nên bất đồng bộ. Thread.sleep(), InputStream.read() và các lời gọi chặn khác vẫn sẽ chặn luồng. Sử dụng withContext(Dispatchers.IO) để bọc các hoạt động chặn.
Nếu một hàm không gọi các hàm suspend khác và không thực hiện hoạt động bất đồng bộ — bổ từ suspend là thừa. Nó thêm chi phí cho máy trạng thái và hạn chế ngữ cảnh gọi. Chỉ làm hàm suspend khi nó thực sự tạm dừng.
Khi một coroutine bị hủy, các hàm suspend ném ra CancellationException. Đừng bắt nó một cách vô thức — bạn đang tước đi khả năng hoàn thành hủy bỏ một cách chính xác của mã gọi. Nếu bạn cần thực hiện một hoạt động kết thúc, hãy sử dụng khối finally và NonCancellable.
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
Khối finally luôn thực thi, bao gồm cả khi hủy bỏ, và NonCancellable đảm bảo việc dọn dẹp không bị gián đoạn.
Bạn không thể gọi trực tiếp hàm suspend từ callback mà không tạo coroutine. Sử dụng suspendCoroutine hoặc suspendCancellableCoroutine để điều chỉnh kiểu callback sang coroutine.
Câu hỏi thường gặp
Có, về mặt kỹ thuật, một hàm suspend có thể không gọi các hàm suspend khác. Trình biên dịch sẽ tạo một máy trạng thái với một trạng thái duy nhất (label 0). Tuy nhiên, không có lợi ích thực tế nào trong hàm như vậy — nó thực thi như hàm thông thường nhưng có chi phí. Đừng sử dụng suspend không cần thiết.
Kotlin cung cấp kotlinx-coroutines-debug — một thư viện với DebugProbes và các công cụ theo dõi coroutine. Trong Android Studio từ Arctic Fox, có tab Coroutines tích hợp trong Trình gỡ lỗi hiển thị các coroutine đang hoạt động, trạng thái và điểm tạm dừng của chúng.
Mỗi điểm tạm dừng tạo một trạng thái mới trong máy trạng thái. Đối với hầu hết các ứng dụng, chi phí của một điểm là vài nano giây (Kotlin 1.9+). Chỉ khi có hàng chục nghìn điểm trong một vòng lặp, bạn mới nên cân nhắc kết hợp các hoạt động hoặc sử dụng sequence/flow.
Trong Kotlin, suspend là bổ từ kiểu hàm, không phải là dấu hiệu của giá trị trả về (như async trong C#). Bất kỳ hàm suspend nào cũng có thể có bất kỳ tham số và kiểu trả về nào, và việc gọi nó về mặt cú pháp không khác gì gọi hàm thông thường — không có toán tử await tại điểm gọi.
Sử dụng suspendCancellableCoroutine để điều chỉnh. Bên trong, bạn đăng ký một callback gọi continuation.resume() và trả về một token hủy bỏ nếu callback hỗ trợ hủy đăng ký. Đây là mẫu tiêu chuẩn để bọc các API Android cũ.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm