Structured Concurrency
1. Giới thiệu
Phần tiêu đề “1. Giới thiệu”Structured Concurrency đảm bảo rằng coroutines được tổ chức theo cấu trúc phân cấp. Khi một coroutine scope kết thúc, tất cả child coroutines cũng kết thúc. Điều này ngăn chặn memory leaks và đảm bảo proper cleanup.
2. Coroutine Hierarchy
Phần tiêu đề “2. Coroutine Hierarchy”import kotlinx.coroutines.*
fun main() = runBlocking { // Parent scope println("Parent started")
launch { // Child 1 println("Child 1 started") delay(1000) println("Child 1 finished") }
launch { // Child 2 println("Child 2 started") delay(500) println("Child 2 finished") }
println("Parent waiting for children...") // Tự động đợi tất cả children hoàn thành}// Output:// Parent started// Parent waiting for children...// Child 1 started// Child 2 started// Child 2 finished// Child 1 finished3. coroutineScope vs runBlocking
Phần tiêu đề “3. coroutineScope vs runBlocking”import kotlinx.coroutines.*
suspend fun doWork() = coroutineScope { // coroutineScope: suspending, không block thread launch { delay(100) println("Task 1 done") } launch { delay(200) println("Task 2 done") } println("coroutineScope started")}
fun main() = runBlocking { // runBlocking: blocks current thread println("Before doWork") doWork() println("After doWork - all children completed")}// Output:// Before doWork// coroutineScope started// Task 1 done// Task 2 done// After doWork - all children completedSo sánh
Phần tiêu đề “So sánh”| Feature | runBlocking |
coroutineScope |
|---|---|---|
| Blocks thread | Yes | No |
| Suspending | No | Yes |
| Use case | Main function, tests | Inside suspend functions |
4. CoroutineScope và lifecycles
Phần tiêu đề “4. CoroutineScope và lifecycles”import kotlinx.coroutines.*
class UserService { // Tạo scope với lifecycle riêng private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun fetchUser(userId: String) { scope.launch { val user = fetchFromNetwork(userId) processUser(user) } }
fun cleanup() { // Cancel tất cả coroutines khi không cần nữa scope.cancel() }
private suspend fun fetchFromNetwork(id: String): String { delay(1000) return "User $id" }
private fun processUser(user: String) { println("Processing: $user") }}Trong ViewModel (Android)
Phần tiêu đề “Trong ViewModel (Android)”class MyViewModel : ViewModel() { // viewModelScope tự động cancel khi ViewModel bị destroy fun loadData() { viewModelScope.launch { val data = repository.fetchData() _uiState.value = UiState.Success(data) } }}5. Job Hierarchy
Phần tiêu đề “5. Job Hierarchy”import kotlinx.coroutines.*
fun main() = runBlocking { val parentJob = coroutineContext[Job] println("Parent job: $parentJob")
val childJob = launch { println("Child job: ${coroutineContext[Job]}") println("Child's parent: ${coroutineContext[Job]?.parent}") delay(1000) }
println("Is child of parent: ${childJob.parent == parentJob}") // Is child of parent: true}6. Cancellation Propagation
Phần tiêu đề “6. Cancellation Propagation”Parent cancels children
Phần tiêu đề “Parent cancels children”import kotlinx.coroutines.*
fun main() = runBlocking { val job = launch { launch { try { delay(Long.MAX_VALUE) } finally { println("Child 1 cancelled") } }
launch { try { delay(Long.MAX_VALUE) } finally { println("Child 2 cancelled") } } }
delay(100) job.cancel() // Cancels parent -> all children cancelled job.join() println("Parent and children cancelled")}// Output:// Child 1 cancelled// Child 2 cancelled// Parent and children cancelledChild failure cancels parent (default)
Phần tiêu đề “Child failure cancels parent (default)”import kotlinx.coroutines.*
fun main() = runBlocking { try { coroutineScope { launch { delay(100) throw RuntimeException("Child failed!") }
launch { try { delay(Long.MAX_VALUE) } finally { println("Sibling cancelled due to child failure") } } } } catch (e: RuntimeException) { println("Caught: ${e.message}") }}// Output:// Sibling cancelled due to child failure// Caught: Child failed!7. SupervisorJob - Isolate Failures
Phần tiêu đề “7. SupervisorJob - Isolate Failures”SupervisorJob ngăn child failure lan truyền:
import kotlinx.coroutines.*
fun main() = runBlocking { // supervisorScope: child failures don't affect siblings supervisorScope { val child1 = launch { println("Child 1 started") delay(100) throw RuntimeException("Child 1 failed") }
val child2 = launch { try { delay(Long.MAX_VALUE) } finally { println("Child 2 completed normally or cancelled") } }
child1.join() println("Child 1 finished with: ${child1.isCancelled}")
delay(100) child2.cancel() }
println("Supervisor scope completed")}// Output:// Child 1 started// Child 1 finished with: true (failed)// Child 2 completed normally or cancelled// Supervisor scope completedSo sánh coroutineScope vs supervisorScope
Phần tiêu đề “So sánh coroutineScope vs supervisorScope”import kotlinx.coroutines.*
suspend fun regularScope() = coroutineScope { // Nếu 1 child fail -> tất cả siblings bị cancel launch { /* ... */ } launch { throw Exception() } // Cancels sibling}
suspend fun supervisorScopeExample() = supervisorScope { // Child fail KHÔNG ảnh hưởng siblings launch { delay(1000) println("This still runs!") } launch { throw Exception() } // Sibling continues}8. GlobalScope - Avoid!
Phần tiêu đề “8. GlobalScope - Avoid!”import kotlinx.coroutines.*
// ❌ GlobalScope - không có structured concurrencyfun badExample() { GlobalScope.launch { // Coroutine này tồn tại độc lập // Không bị cancel khi component bị destroy // Memory leak potential! while (true) { println("Still running...") delay(1000) } }}
// ✅ Use scoped coroutinesclass MyComponent { private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
fun start() { scope.launch { while (isActive) { println("Working...") delay(1000) } } }
fun stop() { scope.cancel() // Properly cleanup }}9. Coroutine Context Inheritance
Phần tiêu đề “9. Coroutine Context Inheritance”import kotlinx.coroutines.*
fun main() = runBlocking(CoroutineName("Parent")) { println("Parent: ${coroutineContext[CoroutineName]}")
launch { // Inherits CoroutineName from parent println("Child inherits: ${coroutineContext[CoroutineName]}") }
launch(CoroutineName("CustomChild")) { // Override với context riêng println("Child overrides: ${coroutineContext[CoroutineName]}") }
launch(Dispatchers.IO) { // Inherit CoroutineName, override Dispatcher println("IO child: ${coroutineContext[CoroutineName]}, ${coroutineContext[ContinuationInterceptor]}") }}// Output:// Parent: CoroutineName(Parent)// Child inherits: CoroutineName(Parent)// Child overrides: CoroutineName(CustomChild)// IO child: CoroutineName(Parent), Dispatchers.IO10. Practical Patterns
Phần tiêu đề “10. Practical Patterns”Parallel decomposition
Phần tiêu đề “Parallel decomposition”import kotlinx.coroutines.*
suspend fun loadUserData(userId: String): UserData = coroutineScope { // Run in parallel val userDeferred = async { fetchUser(userId) } val postsDeferred = async { fetchPosts(userId) } val friendsDeferred = async { fetchFriends(userId) }
// Wait for all UserData( user = userDeferred.await(), posts = postsDeferred.await(), friends = friendsDeferred.await() )}
data class UserData(val user: String, val posts: List<String>, val friends: List<String>)
suspend fun fetchUser(id: String): String { delay(1000) return "User $id"}
suspend fun fetchPosts(id: String): List<String> { delay(800) return listOf("Post 1", "Post 2")}
suspend fun fetchFriends(id: String): List<String> { delay(600) return listOf("Friend 1", "Friend 2")}Timeout với structured concurrency
Phần tiêu đề “Timeout với structured concurrency”import kotlinx.coroutines.*
suspend fun fetchWithTimeout(): String? { return withTimeoutOrNull(1000) { // All child coroutines are cancelled if timeout val data = async { fetchData() } val extra = async { fetchExtraInfo() }
"${data.await()} + ${extra.await()}" }}
suspend fun fetchData(): String { delay(500) return "Data"}
suspend fun fetchExtraInfo(): String { delay(300) return "Extra"}
fun main() = runBlocking { println(fetchWithTimeout()) // "Data + Extra"}Retry with scope
Phần tiêu đề “Retry with scope”import kotlinx.coroutines.*
suspend fun <T> retryWithScope( times: Int, delayMs: Long = 1000, block: suspend CoroutineScope.() -> T): T { repeat(times - 1) { attempt -> try { return coroutineScope { block() } } catch (e: Exception) { println("Attempt ${attempt + 1} failed: ${e.message}") delay(delayMs) } } // Last attempt return coroutineScope { block() }}
fun main() = runBlocking { var attempts = 0 val result = retryWithScope(3) { attempts++ if (attempts < 3) throw RuntimeException("Failed attempt $attempts") "Success on attempt $attempts" } println(result) // Success on attempt 3}📝 Tóm tắt
Phần tiêu đề “📝 Tóm tắt”| Concept | Description |
|---|---|
| Structured Concurrency | Coroutines có hierarchy, parent đợi children |
coroutineScope |
Suspending scope, đợi tất cả children |
supervisorScope |
Child failures không affect siblings |
Job |
Lifecycle handle cho coroutine |
SupervisorJob |
Job không propagate child failures |
| Context inheritance | Children inherit parent’s context |
Best practices:
- Avoid
GlobalScope- use scoped coroutines - Dùng
supervisorScopekhi children independent - Dùng
coroutineScopekhi cần “all or nothing” - Luôn cancel scope khi component bị destroy
- Prefer
viewModelScope,lifecycleScopetrong Android