Skip to Content
Kotlin⚡ Kotlin CoroutinesStructured Concurrency

Structured Concurrency trong Kotlin

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

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 finished

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 completed

So sánh

FeaturerunBlockingcoroutineScope
Blocks threadYesNo
SuspendingNoYes
Use caseMain function, testsInside suspend functions

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)

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

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

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 cancelled

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

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 completed

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!

import kotlinx.coroutines.* // ❌ GlobalScope - không có structured concurrency fun 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 coroutines class 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

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

10. Practical Patterns

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

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

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

ConceptDescription
Structured ConcurrencyCoroutines có hierarchy, parent đợi children
coroutineScopeSuspending scope, đợi tất cả children
supervisorScopeChild failures không affect siblings
JobLifecycle handle cho coroutine
SupervisorJobJob không propagate child failures
Context inheritanceChildren inherit parent’s context

Best practices:

  • Avoid GlobalScope - use scoped coroutines
  • Dùng supervisorScope khi children independent
  • Dùng coroutineScope khi cần “all or nothing”
  • Luôn cancel scope khi component bị destroy
  • Prefer viewModelScope, lifecycleScope trong Android
Last updated on