이전에 티스토리에 작성해두었던 부분을 옮겨왔습니다.
중첩 코루틴 내부에서 오류가 발생하면 앱이 비정상적으로 종료되는 현상이 생겼습니다.
이런 현상을 방지하기 위해 Coroutine 예외처리 코드가 필요했습니다.
// Coroutines Error 가 발생하는 코드
suspend fun function(): ResultType = coroutineScope {
val deferred1 = async { someWork() }
val deferred2 = async {
val result = deferred1.await()
anotherWork(result) // 중첩된 비동기 작업
}
deferred2.await()
}
launch
타입의 빌더로 생성된 코루틴이 루트 코루틴인 경우, 코루틴 계층 구조 내에서 발생한 예외는 루트 코루틴의 예외 핸들러에 의해 처리됩니다.
async
타입의 빌더로 생성된 코루틴이 루트 코루틴인 경우, 코루틴 계층 구조 내에서 발생한 예외는 호출자에게 예외 처리를 맡기며 루트 코루틴의 예외 핸들러는 동작하지 않습니다. (호출자가 await()
, receive()
등의 데이터 수신 함수를 호출할 때 예외가 발생함)
async
로 생성한 코루틴을 async
에서 await()
하면 예외가 발생한 코루틴에서 처리하기 때문에 앱이 비정상적으로 종료되는 현상은 발생하지 않습니다.
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.async {
Log.d(TAG, "${comApiRepository.function()}")
}
}
하지만 만약 async
로 생성한 코루틴을 예외 핸들러를 달지 않은 launch
블럭에서 await()
하면 예외가 전파되면서 앱이 비정상적으로 종료되는 현상이 발생합니다.
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.launch {
Log.d(TAG, "${comApiRepository.function()}")
}
}
--------- beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.naram.dynamicurlapi, PID: 6508
kotlin.KotlinNullPointerException: Response from com.rit.data.remote.service.ApiService.someWork was null but response body type was declared as non-null
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:43)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:535)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@f3485f9, Dispatchers.Main.immediate]
CoroutineExceptionHandler
사용/**
* MainViewModel.kt
*/
private val _state = MutableLiveData<State>()
val state: LiveData<State>
get() = _state
// CoroutineExceptionHandler 생성
protected val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
Log.e("CoroutineExceptionHandler", "${exception.message}")
when(exception) {
is SocketException -> _state.postValue(State.BAD_INTERNET)
is HttpException -> _state.postValue(State.PARSE_ERROR)
is UnknownHostException -> _state.postValue(State.WRONG_CONNECTION)
else -> _state.postValue(State.FAIL)
}
}
private fun getData() {
// 루트 코루틴에 CoroutineExceptionHandler 달기
viewModelScope.launch(coroutineExceptionHandler) {
Log.d(TAG, "${comApiRepository.function()}")
}
}
runCatching
구문async
인 경우/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.async {
Log.d(TAG, "${comApiRepository.functionErrorHandlingResult()}")
}
}
/**
* RepositoryImpl.kt
*/
suspend fun functionErrorHandlingResult(): ApiResponse? {
coroutineScope {
val someWork = async { someWork() }
val anotherWork = async { anotherWork(someWork.await()) }
return kotlin.runCatching {
anotherWork.await()
}.onSuccess {
Log.d(this::class.java.name, "$it")
}.onFailure {
Log.e(this::class.java.name, "${it.message}")
}.getOrThrow()
}
}
launch
인 경우/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.launch {
Log.d(TAG, "${comApiRepository.functionErrorHandlingResult()}")
}
}
/**
* RepositoryImpl.kt
*/
suspend fun functionErrorHandlingResult(): ApiResponse? {
coroutineScope {
val someWork = async { someWork() }
val anotherWork = async { anotherWork(someWork.await()) }
return kotlin.runCatching {
anotherWork.await()
}.onSuccess {
Log.d(this::class.java.name, "$it")
}.onFailure {
Log.e(this::class.java.name, "${it.message}")
}.getOrThrow()
}
}
runCatching
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.launch {
kotlin.runCatching {
comApiRepository.function()
}.onSuccess {
Log.d(TAG, "$it")
}.onFailure {
Log.e(TAG, "${it.message}")
}
}
}
코루틴 예외 다루기
[Kotlin] 코루틴의 취소와 예외 처리 파고들기
runCatching을 이용한 kotlin에서 exception처리 방법