Exception Handling
1. Giới thiệu
Phần tiêu đề “1. Giới thiệu”Exception handling trong coroutines khác với code thông thường do structured concurrency. Exceptions propagate theo hierarchy và có thể cancel siblings.
2. Basic Exception Handling
Phần tiêu đề “2. Basic Exception Handling”try-catch trong coroutine
Phần tiêu đề “try-catch trong coroutine”import kotlinx.coroutines.*
fun main() = runBlocking { launch { try { riskyOperation() } catch (e: Exception) { println("Caught in coroutine: ${e.message}") } }}
suspend fun riskyOperation() { delay(100) throw RuntimeException("Something went wrong!")}try-catch KHÔNG work xung quanh launch
Phần tiêu đề “try-catch KHÔNG work xung quanh launch”import kotlinx.coroutines.*
fun main() = runBlocking { // ❌ KHÔNG catch được exception từ launch try { launch { throw RuntimeException("Failed!") } } catch (e: Exception) { println("This won't catch it!") }
delay(100) println("This might not print due to uncaught exception")}3. CoroutineExceptionHandler
Phần tiêu đề “3. CoroutineExceptionHandler”Handler cuối cùng cho uncaught exceptions:
import kotlinx.coroutines.*
fun main() = runBlocking { val handler = CoroutineExceptionHandler { context, exception -> println("Caught in handler: ${exception.message}") println("Coroutine: ${context[CoroutineName]}") }
val scope = CoroutineScope(SupervisorJob() + handler + CoroutineName("MainScope"))
scope.launch(CoroutineName("FailingJob")) { throw RuntimeException("Oops!") }
scope.launch(CoroutineName("SuccessJob")) { delay(100) println("This still runs with SupervisorJob!") }
delay(200)}// Output:// Caught in handler: Oops!// Coroutine: CoroutineName(FailingJob)// This still runs with SupervisorJob!Handler rules
Phần tiêu đề “Handler rules”import kotlinx.coroutines.*
fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") }
// ✅ Handler works with launch val scope = CoroutineScope(SupervisorJob() + handler) scope.launch { throw RuntimeException("launch exception") }
delay(100)
// ❌ Handler KHÔNG work với async - dùng try-catch với await() val deferred = scope.async { throw RuntimeException("async exception") }
try { deferred.await() } catch (e: Exception) { println("Caught from async: ${e.message}") }}4. Exception Propagation
Phần tiêu đề “4. Exception Propagation”Default behavior: fail fast
Phần tiêu đề “Default behavior: fail fast”import kotlinx.coroutines.*
fun main() = runBlocking { try { coroutineScope { launch { delay(50) throw RuntimeException("Child 1 failed") }
launch { try { delay(Long.MAX_VALUE) } catch (e: CancellationException) { println("Child 2 cancelled due to sibling failure") throw e // Re-throw to complete cancellation } } } } catch (e: RuntimeException) { println("Parent caught: ${e.message}") }}// Output:// Child 2 cancelled due to sibling failure// Parent caught: Child 1 failedSupervisorScope: isolate failures
Phần tiêu đề “SupervisorScope: isolate failures”import kotlinx.coroutines.*
fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, e -> println("Handler caught: ${e.message}") }
supervisorScope { launch(handler) { delay(50) throw RuntimeException("Child 1 failed") }
launch { delay(100) println("Child 2 completed successfully!") } }
println("Supervisor scope completed")}// Output:// Handler caught: Child 1 failed// Child 2 completed successfully!// Supervisor scope completed5. async Exception Handling
Phần tiêu đề “5. async Exception Handling”import kotlinx.coroutines.*
fun main() = runBlocking { // async defers exception until await() val deferred = async { delay(100) throw RuntimeException("Async failed") }
// Exception thrown here try { val result = deferred.await() } catch (e: RuntimeException) { println("Caught at await: ${e.message}") }}Parallel async với error handling
Phần tiêu đề “Parallel async với error handling”import kotlinx.coroutines.*
suspend fun fetchAllData(): Result<Data> = coroutineScope { try { val users = async { fetchUsers() } val posts = async { fetchPosts() }
Result.success(Data(users.await(), posts.await())) } catch (e: Exception) { Result.failure(e) }}
data class Data(val users: List<String>, val posts: List<String>)
suspend fun fetchUsers(): List<String> { delay(100) return listOf("Alice", "Bob")}
suspend fun fetchPosts(): List<String> { delay(150) throw RuntimeException("Network error!")}
fun main() = runBlocking { when (val result = fetchAllData()) { is Result -> result.fold( onSuccess = { println("Data: $it") }, onFailure = { println("Error: ${it.message}") } ) }}// Output: Error: Network error!6. CancellationException
Phần tiêu đề “6. CancellationException”Cancellation là đặc biệt - không được coi là failure:
import kotlinx.coroutines.*
fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, e -> println("Handler: ${e.message}") // NOT called for cancellation }
val job = launch(handler) { try { delay(Long.MAX_VALUE) } catch (e: CancellationException) { println("Caught cancellation: ${e.message}") throw e // Re-throw is OK (và recommended) } }
delay(100) job.cancel(CancellationException("Cancelled by user")) job.join()
println("Job cancelled, but no exception handler called")}// Output:// Caught cancellation: Cancelled by user// Job cancelled, but no exception handler calledCancellation trong finally
Phần tiêu đề “Cancellation trong finally”import kotlinx.coroutines.*
fun main() = runBlocking { val job = launch { try { delay(Long.MAX_VALUE) } finally { // Cleanup code println("Cleaning up...")
// ❌ Suspending calls throw here normally // delay(100) // throws CancellationException
// ✅ Use withContext(NonCancellable) for cleanup withContext(NonCancellable) { delay(100) println("Cleanup complete") } } }
delay(100) job.cancelAndJoin()}7. Retry Patterns
Phần tiêu đề “7. Retry Patterns”Simple retry
Phần tiêu đề “Simple retry”import kotlinx.coroutines.*
suspend fun <T> retry( times: Int = 3, delayMs: Long = 1000, block: suspend () -> T): T { var lastException: Exception? = null
repeat(times) { attempt -> try { return block() } catch (e: Exception) { lastException = e println("Attempt ${attempt + 1} failed: ${e.message}") if (attempt < times - 1) { delay(delayMs) } } }
throw lastException!!}
suspend fun unreliableCall(): String { if (Math.random() < 0.7) { throw RuntimeException("Network error") } return "Success!"}
fun main() = runBlocking { try { val result = retry(times = 5, delayMs = 500) { unreliableCall() } println("Result: $result") } catch (e: Exception) { println("All attempts failed: ${e.message}") }}Exponential backoff
Phần tiêu đề “Exponential backoff”import kotlinx.coroutines.*import kotlin.math.pow
suspend fun <T> retryWithBackoff( times: Int = 3, initialDelayMs: Long = 100, maxDelayMs: Long = 10000, factor: Double = 2.0, block: suspend () -> T): T { var currentDelay = initialDelayMs
repeat(times - 1) { attempt -> try { return block() } catch (e: Exception) { println("Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms") delay(currentDelay) currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelayMs) } }
return block() // Last attempt}8. Flow Error Handling
Phần tiêu đề “8. Flow Error Handling”catch operator
Phần tiêu đề “catch operator”import kotlinx.coroutines.*import kotlinx.coroutines.flow.*
fun riskyFlow(): Flow<Int> = flow { emit(1) emit(2) throw RuntimeException("Flow error!") emit(3) // Never reached}
fun main() = runBlocking { riskyFlow() .catch { e -> println("Caught: ${e.message}") emit(-1) // Emit fallback value } .collect { println("Value: $it") }}// Output:// Value: 1// Value: 2// Caught: Flow error!// Value: -1retry operator
Phần tiêu đề “retry operator”import kotlinx.coroutines.*import kotlinx.coroutines.flow.*
var attempts = 0
fun unreliableFlow(): Flow<Int> = flow { attempts++ if (attempts < 3) { throw RuntimeException("Attempt $attempts failed") } emit(42)}
fun main() = runBlocking { unreliableFlow() .retry(3) { e -> println("Retrying due to: ${e.message}") delay(100) true // true = retry, false = don't retry } .catch { println("All retries failed: ${it.message}") } .collect { println("Result: $it") }}// Output:// Retrying due to: Attempt 1 failed// Retrying due to: Attempt 2 failed// Result: 429. Best Practices
Phần tiêu đề “9. Best Practices”import kotlinx.coroutines.*
// ✅ Handle exceptions at the right levelclass Repository { suspend fun fetchData(): Result<Data> { return try { Result.success(actualFetch()) } catch (e: NetworkException) { Result.failure(e) } }}
// ✅ Use supervisorScope for independent taskssuspend fun loadDashboard() = supervisorScope { val news = async { fetchNews() } // Independent val weather = async { fetchWeather() } // Independent
DashboardData( news = runCatching { news.await() }.getOrDefault(emptyList()), weather = runCatching { weather.await() }.getOrNull() )}
// ✅ Always handle CancellationException properlysuspend fun doWork() { try { // work } catch (e: CancellationException) { // Cleanup if needed throw e // Always re-throw! } catch (e: Exception) { // Handle other exceptions }}
// ✅ Use NonCancellable for critical cleanupsuspend fun saveAndClose(resource: Resource) { try { resource.use() } finally { withContext(NonCancellable) { resource.save() resource.close() } }}10. Summary Table
Phần tiêu đề “10. Summary Table”| Scope | Exception Behavior |
|---|---|
coroutineScope |
One fails -> all cancel |
supervisorScope |
Failures isolated |
launch |
Propagates to parent |
async |
Deferred until await() |
| Handler | Works with |
|---|---|
try-catch |
Inside coroutine, or around await() |
CoroutineExceptionHandler |
launch (not async) |
Flow catch |
Flow emissions |
📝 Tóm tắt
Phần tiêu đề “📝 Tóm tắt”- Dùng
try-catchbên trong coroutines hoặc xung quanhawait() CoroutineExceptionHandlercho uncaught exceptions từlaunchsupervisorScopekhi tasks độc lậpCancellationExceptionnên được re-throwNonCancellablecho critical cleanup- Flow: dùng
catchvàretryoperators