Cancellation
코루틴은 내부적으로 취소 동작을 위해 CancellationException
을 사용한다.
- 모든
Handler
가 무시하여Handler
등록하여도 동작하지 않는다. try-catch
를 통해 처리할 경우 취소 및 종료시에 자원 반납이 대한 이중 코드가 되기에try-finally
에서 한번에 처리해야 한다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val parent = launch {
val child = launch {
try {
delay(Long.MAX_VALUE)
} catch (e :CancellationException) {
println("Child Cancelled")
}
}
yield() // Thread 순서 양보
println("Start Child Cancel")
child.cancelAndJoin()
yield()
println("Parent Not Cancelled")
}
parent.join()
}
// Start Child Cancel
// Child Canceld
// Parent Not Cancel
Exception
코루틴은 CancellationException
이외의 예외를 만나면 양방향 처리(부모 → 자식, 자식 → 부모)한다.
- 부모에서 예외 발생 시 모든 자식을 종료시키고 부모 코루틴에 의해 처리된다.
- 자식에서 예외 발생 시 부모 및 모든 자식을 종료시키고 부모 코루틴에 의해 처리된다.
import kotlinx.coroutines.*
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
fun main() = runBlocking {
val parent = GlobalScope.launch(handler) {
launch {
try {
delay(Long.MAX_VALUE)
} finally {
withContext(NonCancellable) {
println("First Child Canceled")
delay(2000)
println("First Child Finished")
}
}
}
launch {
delay(1000)
println("Second Child Throw Exception")
throw Exception()
}
}
parent.join()
}
// Second Child Throw Exception
// First Child Canceled
// First Child Finished
// Caught java.lang.Exception
SupervisorJob
예외를 양방향 처리(부모 → 자식, 자식 → 부모)하는 Job
객체와는 다르게 예외를 단방향 처리(부모 → 자식)하는 Job
객체이다.
부모 코루틴 예외 발생
부모 코루틴에서 예외 발생 시 모든 자식이 종료된다.
import kotlinx.coroutines.*
fun main() = runBlocking {
with(CoroutineScope(coroutineContext)) {
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("First Child Failed")
throw Exception("Caught Exception") // 에러로 인해 취소되고 부모 코루틴으로 전파
}
val secondChild = launch { // 부모 코루틴이 취소되었기에 자식 코루틴에게도 취소가 전파됨
delay(1000)
println("First Child State: ${firstChild.isCancelled}")
println("Second Child")
}
firstChild.join()
secondChild.join()
}
}
// First Child Failed
// Exception in thread "main" java.lang.Exception: Caught Exception
자식 코루틴 예외 발생
자식 코루틴에서 예외 발생 시 해당 Job
만 종료된다.
import kotlinx.coroutines.*
val supervisor = SupervisorJob()
fun main() = runBlocking {
with(CoroutineScope(coroutineContext + supervisor)) {
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("First Child Failed")
throw Exception("Caught Exception") // 에러로 인해 취소되지만 부모 코루틴으로 전파 안함
}
val secondChild = launch {
delay(1000)
println("First Child State: ${firstChild.isCancelled}")
println("Second Child") // 작업 완료
}
firstChild.join()
secondChild.join()
}
}
// First Child Failed
// First Child State: true
// Second Child
supervisorScope
coroutineScope
와는 다르게 예외를 단방향 처리(부모 → 자식)하는 CoroutineScope
이다.
블록 내부의 모든 코루틴에 SupervisorJob
을 설정하고 싶을 경우에 주로 사용한다.
import kotlinx.coroutines.*
fun main() = runBlocking {
supervisorScope {
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("First Child Failed") // 에러로 인해 취소되지만 부모 코루틴으로 전파 안함
throw Exception("Caught Exception")
}
val secondChild = launch {
delay(1000)
println("First Child State: ${firstChild.isCancelled}")
println("Second Child") // 작업 완료
}
firstChild.join()
secondChild.join()
}
}
// First Child Failed
// First Child State: true
// Second Child
CoroutineExceptionHandler
코루틴에서 예외 발생에 대한 처리를 할 수 있는 CoroutineContext
이다.
스레드에서 캐치되지 않은 런타임 예외를 한 곳에서 처리할 수 있도록 도와주는 자바의 Thread.defaultUncaughtExceptionHandler
와 비슷하다.
launch
launch
는 예외 발생 시 등록한 CoroutineExceptionHandler
에 위임하여 콜백으로 예외 처리가 가능하다.
import kotlinx.coroutines.*
val exceptionHandler = CoroutineExceptionHandler { coroutineScope, exception ->
Log.e("ERROR", "${exception.message}")
}
val ioDispatchers = Dispatchers.IO + exceptionHandler
GlobalScope.launch(ioDispatchers) {
throw Exception()
}
async / withContext
async
, withContext
는 외부에 try-catch
로 예외 처리가 가능하다.
import kotlinx.coroutines.*
fun error() {
try {
GlobalScope.async {
throw Exception()
}
} catch (e: Exception) {
Log.e("ERROR", "${exception.message}")
}
}
GloablScope.launch(Dispatchers.IO) {
try {
withContext(Dispatchers.Main) {
throw Exception()
}
} catch (e: Exception) {
Log.e("ERROR", "${exception.message}")
}
}