안드로이드 뷰 계층을 생성하지 않은 쓰레드가 관련 뷰를 업데이트하려고 할 때마다 CalledFromWrongThreadException
을 발생시킨다.
이 예외는 UI쓰레드가 아닌 다른 쓰레드가 뷰를 업데이트 할 때마다 발생한다.
UI쓰레드만이 뷰 계층을 생성하고 업데이트 할 수 있다.
자바에서의 네트워크 동작은 기본적으로 블로킹된다.
UI쓰레드가 블로킹 된다는 것은 애니메이션이나 기타 상호작용을 포함한 모든 UI가 멈춘다는 것을 의미하므로, UI 쓰레드에서 네트워크 작업을 수행할 때 마다 안드로이드는 중단된다.
이런 상황이 발생할 때마다 NetworkOnMainThreadException
이 발생하는데 백그라운드 쓰레드를 사용해서 사용자의 경험을 개선해야 한다.
⭐ 백그라운드에서 요청하고 UI쓰레드에서 업데이트 할 것
결과 처리를 위한 목적으로 코루틴을 시작했다면 async()
를 사용해야 한다.
async()
는 Defferred<T>
를 반환하는데, 디퍼드 코루틴 프레임워크에서 제공하는 취소 불가능한 넌 블로킹 퓨처를 의미하며, T는 결과의 유형을 나타낸다.
fun main() = runBlocking {
val task = GlobalScope.async {
doSomething()
}
task.join()
println("Completed")
} // End of main
private fun doSomething() {
throw UnsupportedOperationException("Can't do")
} // End of doSomething()
Exception이 발생하지 않았고 결과를 출력했다.
async()
블록 안에서 발생하는 예외는 그 결과에 첨부되는데, 그 결과를 확인해야 예외를 찾을 수 있다.
이를 위해서 isCancelled
와 getCancellationException()
메소드를 함께 사용해 안전하게 예외를 가져올 수 있다.
fun main() = runBlocking {
val task = GlobalScope.async {
doSomething()
}
task.join()
if (task.isCancelled) {
val exception = task.getCancellationException()
println("Error with message : ${exception.cause}")
} else {
println("Success")
}
} // End of main
이렇게 task.isCancelled
를 사용해서 오류를 출력할 수 있다.
예외를 전파하기 위해서는 Defferred에서 await()
을 호출할 수 있다.
fun main() = runBlocking {
val task = GlobalScope.async {
doSomething()
}
task.await()
println("Completed")
} // End of main
await()
을 호출해서 중단되는데 이 경우가 예외를 감싸지 않고 전파하는, unwrapping deferred이다.
join()
으로 대기한 후 검증하고 어떤 오류를 처리하는 것과 await()
을 직접 호출하는 방식의 주요 차이는 join()
은 예외를 전파하지 않고 처리하는 반면, await()
은 단지 호출하는 것만으로 예외가 전파된다는 점이다.
await()
를 사용한 예제는 실행 중 에러를 의미하는 코드 1을 반환하는 반면, join()
으로 대기하고 iscancelled
와 getCancellaaationException()
을 사용해 에러를 처리한 경우는 성공을 의미하는 코드 0이 나온다.
결과를 반환하지 않는 코루틴을 시작하려면 launch()
를 사용해야 한다. launch()
는 연산이 실패한 경우에만 통보 받기를 원하는 fire-and-forget 시나리오를 위해 설계되었으며, 필요할 때 취소할 수 있는 함수도 함께 제공된다.
fire-and-forget scenario, 이벤트나 메세지 기반 시스템에서 널리 활용되는 패턴으로, 미사일을 발사하고 나면 그 후 미사일은 알아서 표적을 향해 날아가는데 마시일에 대해 잊고 있어도(Forget) 알아서 표적에 명중한다는 것으로, 여기서는 실행 후 결과에 대해 신경 쓸 필요 없는 경우를 의미한다.
fun main() = runBlocking {
val task = GlobalScope.launch {
doSomething()
}
task.join()
println("Completed")
} // End of main
예상한 대로 예외가 스택에 출력되지만 실행이 중단되지 않았고, main()
이 실행 완료되었다는 메세지가 출력되었다.