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ì?
Phần tiêu đề “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
Phần tiêu đề “Sự khác biệt với hàm thường”// Hàm thường - BLOCK threadfun normalFunction() { Thread.sleep(1000) // Thread bị block trong 1 giây}
// Suspend function - KHÔNG block threadsuspend 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
Phần tiêu đề “Cách khai báo Suspend Function”Cú pháp cơ bản
Phần tiêu đề “Cú pháp cơ bản”suspend fun functionName(): ReturnType { // Code có thể suspend}Ví dụ
Phần tiêu đề “Ví dụ”// Suspend function trả về datasuspend 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
Phần tiêu đề “Suspension Points”Suspension point là điểm trong code nơi coroutine có thể tạm dừng.
Suspension points bao gồm:
Phần tiêu đề “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
Phần tiêu đề “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ườngfun normalFunction() { val data = fetchData() // Compile error!}
// ✅ ĐÚNG - Gọi từ suspend function khácsuspend fun anotherSuspendFunction() { val data = fetchData() // OK!}
// ✅ ĐÚNG - Gọi từ coroutinefun 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).
Code bạn viết:
Phần tiêu đề “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):
Phần tiêu đề “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ế
Phần tiêu đề “Ví dụ thực tế”1. Gọi API
Phần tiêu đề “1. Gọi API”// Repositoryclass 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) } }}
// ViewModelclass 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ự
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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ụngval data = retry(times = 3) { api.getData()}withContext - Chuyển đổi Dispatcher
Phần tiêu đề “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
Phần tiêu đề “📝 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
Phần tiêu đề “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
Phần tiêu đề “Tiếp theo”Học về Coroutine Builders - các cách khởi chạy coroutine.