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

Structured Concurrency

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.

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
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
Feature runBlocking coroutineScope
Blocks thread Yes No
Suspending No Yes
Use case Main function, tests Inside suspend functions
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")
}
}
class MyViewModel : ViewModel() {
// viewModelScope tự động cancel khi ViewModel bị destroy
fun loadData() {
viewModelScope.launch {
val data = repository.fetchData()
_uiState.value = UiState.Success(data)
}
}
}
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
}
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
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!

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
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
}
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
}
}
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
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")
}
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"
}
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
}

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