suspend function: nó là gì, cú pháp và hoạt động trong coroutine

Tác giả: IT Sectr Đã đăng: 2026-06-22 Thời gian đọc: 9 phút

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 — từ khóa Kotlin đánh dấu hàm có thể tạm dừng (bất đồng bộ)
  • Continuation — tham số ẩn mà trình biên dịch thêm vào mỗi hàm suspend để lưu trạng thái
  • Điểm tạm dừng — nơi các hàm suspend khác được gọi, nơi coroutine có thể dừng mà không chặn
  • Máy trạng thái — biểu diễn nội bộ của hàm suspend, nơi mỗi điểm tạm dừng là một trạng thái riêng biệt
  • Chỉ gọi từ coroutine — hàm suspend chỉ có thể được gọi từ hàm suspend khác hoặc từ launch/async

Suspend function trong Kotlin là gì?

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.

kotlin
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.

Lịch sử

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.

Cách hoạt động của hàm suspend: Continuation và máy trạng thái

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.

Continuation — tham số ẩn

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:

  • context — CoroutineContext (bộ điều phối, job, các phần tử ngữ cảnh)
  • resumeWith — phương thức để tiếp tục thực thi với kết quả hoặc ngoại lệ
  • label — chỉ mục của trạng thái hiện tại trong máy trạng thái

Ví dụ về máy trạng thái

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:

kotlin
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:

kotlin
// 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.

Cú pháp hàm suspend: khai báo và gọi

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.

Khai báo cơ bản

kotlin
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.

Gọi từ coroutine

kotlin
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.

Lambda suspend và kiểu hàm

Kotlin hỗ trợ các phiên bản suspend của kiểu hàm — suspend () -> Tsuspend (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:

kotlin
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

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ểmHàm thông thườngHàm suspend
Luồng thực thiChặn luồng cho đến khi hoàn thànhCó thể giải phóng luồng và tiếp tục sau
Tham số trình biên dịchChỉ tham số được chỉ địnhContinuation ngầm ở cuối
Gọi từ hàm thông thườngKhông
Ngăn xếpNgăn xếp vật lý của luồngMá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ếpGiá trị hoặc COROUTINE_SUSPENDED
Hiệu suấtChi phí tối thiểu~vài nano giây cho mỗi máy trạng thái (Kotlin 1.9+)

Tại sao hàm suspend không thể được gọi từ hàm thông thường

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).

Ví dụ về hàm suspend trong Android

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.

Ví dụ 1: Room DAO với truy vấn suspend

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:

kotlin
@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.

Ví dụ 2: Kết hợp các hàm suspend để tải màn hình

kotlin
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.

Ví dụ 3: Các bước bất đồng bộ tuần tự

kotlin
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.

Lỗi thường gặp khi làm việc với hàm suspend

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.

Lỗi 1: Gọi chặn bên trong suspend

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.

Lỗi 2: Tạo hàm suspend không cần thiết

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.

Lỗi 3: Bỏ qua CancellationException

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.

kotlin
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.

Lỗi 4: Gọi hàm suspend từ callback

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

Một hàm suspend có thể không có điểm tạm dừng không?

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.

Làm thế nào để gỡ lỗi hàm suspend?

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.

Số lượng điểm tạm dừng có ảnh hưởng đến hiệu suất khô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.

Hàm suspend khác với async/await trong các ngôn ngữ khác như thế nào?

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.

Làm thế nào để chuyển đổi hàm callback thành suspend?

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

  • Suspend function — hàm có bổ từ suspend có thể tạm dừng thực thi mà không chặn luồng thông qua cơ chế Continuation
  • Máy trạng thái — biểu diễn nội bộ của hàm suspend trong bytecode Kotlin, nơi mỗi điểm tạm dừng là một trạng thái riêng biệt với nhãn
  • Continuation — tham số ẩn chứa ngữ cảnh coroutine và phương thức resumeWith để tiếp tục thực thi
  • Chỉ gọi từ coroutine — hàm suspend không thể truy cập từ hàm thông thường nếu không có runBlocking hoặc CoroutineScope
  • Hoạt động chặn bên trong suspend yêu cầu withContext(Dispatchers.IO) — nếu không luồng sẽ bị chặn
  • Room và Retrofit hỗ trợ hàm suspend một cách tự nhiên, tự động quản lý luồng nền
  • CancellationException — xử lý hủy bỏ qua finally + NonCancellable, đừng bắt CancellationException một cách vô thức

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.

Thảo luận dự án

Đọc thêm