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à gì?
Suspend function là hàm có thể tạm dừng (suspend) và 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!"
}Sự khác biệt với hàm thường
// 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]Cách khai báo Suspend Function
Cú pháp cơ bản
suspend fun functionName(): ReturnType {
// Code có thể suspend
}Ví dụ
// 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 Points
Suspension point là điểm trong code nơi coroutine có thể tạm dừng.
Suspension points bao gồm:
- 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)
}-
Các hàm có sẵn:
delay(),withContext(),yield()… -
Custom suspend functions:
suspend fun fetchFromApi(): Data {
return withContext(Dispatchers.IO) {
api.getData() // Network call
}
}Gọi Suspend Function
Suspend function chỉ có thể được gọi từ:
- Một suspend function khác
- 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)
Kotlin compiler biến suspend function thành Continuation Passing Style (CPS).
Code bạn viết:
suspend fun loadData(): String {
val a = fetchA()
val b = fetchB()
return "$a $b"
}Compiler biến thành (đơn giản hóa):
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
Ví dụ thực tế
1. Gọi API
// 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)
}
}
}
}2. Chạy tuần tự
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)
}3. Chạy song song với async
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()
)
}
}4. Retry với suspend function
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 - Chuyển đổi Dispatcher
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) }
}
}📝 Tóm tắt
| 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 |
Quy tắc
- Suspend function chỉ gọi được từ coroutine hoặc suspend function khác
- Không block thread - chỉ suspend coroutine
- Dùng
withContext(Dispatchers.IO)cho I/O operations - Dùng
withContext(Dispatchers.Default)cho CPU-intensive work
Tiếp theo
Học về Coroutine Builders - các cách khởi chạy coroutine.
Last updated on