Bỏ qua để đến nội dung

Suspend Functions

Suspend function là nền tảng của Kotlin Coroutines. Hiểu rõ cách chúng hoạt động sẽ giúp bạn viết code async hiệu quả.

Suspend function là hàm có thể tạm dừng (suspend)tiếp tục (resume) sau đó mà không block thread hiện tại.

suspend fun fetchData(): String {
delay(1000) // Tạm dừng 1 giây
return "Data loaded!"
}
// Hàm thường - BLOCK thread
fun normalFunction() {
Thread.sleep(1000) // Thread bị block trong 1 giây
}
// Suspend function - KHÔNG block thread
suspend fun suspendFunction() {
delay(1000) // Thread được giải phóng trong 1 giây
}

Minh họa:

Normal Function:
Thread A: [====BLOCKED 1s====][done]
Suspend Function:
Thread A: [suspend][free to do other work...][resume][done]

suspend fun functionName(): ReturnType {
// Code có thể suspend
}
// Suspend function trả về data
suspend fun getUser(id: Int): User {
delay(500) // Giả lập network call
return User(id, "John")
}
// Suspend function không trả về gì
suspend fun saveUser(user: User) {
delay(500)
database.save(user)
}
// Suspend function với nhiều tham số
suspend fun searchUsers(query: String, limit: Int = 10): List<User> {
delay(300)
return repository.search(query, limit)
}

Suspension point là điểm trong code nơi coroutine có thể tạm dừng.

  1. Gọi suspend function khác:
suspend fun loadProfile() {
val user = getUser(123) // Suspension point #1
val posts = getPosts(user) // Suspension point #2
display(user, posts)
}
  1. Các hàm có sẵn: delay(), withContext(), yield()

  2. Custom suspend functions:

suspend fun fetchFromApi(): Data {
return withContext(Dispatchers.IO) {
api.getData() // Network call
}
}

Suspend function chỉ có thể được gọi từ:

  1. Một suspend function khác
  2. Một coroutine (trong launch, async, v.v.)
// ❌ SAI - Không thể gọi từ hàm thường
fun normalFunction() {
val data = fetchData() // Compile error!
}
// ✅ ĐÚNG - Gọi từ suspend function khác
suspend fun anotherSuspendFunction() {
val data = fetchData() // OK!
}
// ✅ ĐÚNG - Gọi từ coroutine
fun startLoading() {
viewModelScope.launch {
val data = fetchData() // OK!
}
}

Cách hoạt động bên trong (CPS Transformation)

Phần tiêu đề “Cách hoạt động bên trong (CPS Transformation)”

Kotlin compiler biến suspend function thành Continuation Passing Style (CPS).

suspend fun loadData(): String {
val a = fetchA()
val b = fetchB()
return "$a $b"
}
fun loadData(continuation: Continuation<String>): Any {
when (continuation.label) {
0 -> {
continuation.label = 1
return fetchA(continuation) // Suspend tại đây
}
1 -> {
val a = continuation.a
continuation.label = 2
return fetchB(continuation) // Suspend tại đây
}
2 -> {
val b = continuation.b
return "$a $b" // Resume và return
}
}
}

Continuation lưu trữ:

  • Trạng thái hiện tại (label)
  • Các biến local
  • Điểm sẽ resume

// Repository
class UserRepository(private val api: ApiService) {
suspend fun getUser(id: Int): User {
return withContext(Dispatchers.IO) {
api.getUser(id)
}
}
suspend fun updateUser(user: User) {
withContext(Dispatchers.IO) {
api.updateUser(user)
}
}
}
// ViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel() {
fun loadUser(id: Int) {
viewModelScope.launch {
try {
val user = repository.getUser(id) // Suspend tại đây
_userState.value = UserState.Success(user)
} catch (e: Exception) {
_userState.value = UserState.Error(e.message)
}
}
}
}
suspend fun loadProfile(userId: Int): Profile {
// Chạy tuần tự: user -> posts -> comments
val user = getUser(userId) // Đợi xong rồi mới
val posts = getUserPosts(user.id) // Đợi xong rồi mới
val comments = getComments(posts) // Tiếp tục
return Profile(user, posts, comments)
}
suspend fun loadDashboard(): Dashboard {
// Chạy song song - nhanh hơn!
return coroutineScope {
val userDeferred = async { getUser() }
val postsDeferred = async { getPosts() }
val notificationsDeferred = async { getNotifications() }
Dashboard(
user = userDeferred.await(),
posts = postsDeferred.await(),
notifications = notificationsDeferred.await()
)
}
}
suspend fun <T> retry(
times: Int,
initialDelay: Long = 100,
maxDelay: Long = 1000,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) {
try {
return block()
} catch (e: Exception) {
// Log error
}
delay(currentDelay)
currentDelay = (currentDelay * 2).coerceAtMost(maxDelay)
}
return block() // Last attempt
}
// Sử dụng
val data = retry(times = 3) {
api.getData()
}

withContext cho phép chạy suspend function trên Dispatcher khác:

suspend fun saveFile(content: String) {
// Chuyển sang IO thread để ghi file
withContext(Dispatchers.IO) {
File("data.txt").writeText(content)
}
// Tự động quay về thread trước đó
}
suspend fun processData(items: List<Item>): Result {
// Chuyển sang Default để tính toán nặng
return withContext(Dispatchers.Default) {
items.map { heavyComputation(it) }
}
}

Khái niệm Mô tả
suspend Keyword đánh dấu hàm có thể tạm dừng
Suspension point Điểm coroutine có thể pause
Continuation Object lưu trạng thái để resume
withContext Chuyển sang Dispatcher khác
  1. Suspend function chỉ gọi được từ coroutine hoặc suspend function khác
  2. Không block thread - chỉ suspend coroutine
  3. Dùng withContext(Dispatchers.IO) cho I/O operations
  4. Dùng withContext(Dispatchers.Default) cho CPU-intensive work

Học về Coroutine Builders - các cách khởi chạy coroutine.