코루틴은 콜백을 사용하지 않고 비동기 처리를 해 주는 장점을 가지고 있음
suspendCancellableCoroutine의 정의는 아래 코드와 같다
inline suspend fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit): T
람다의 파라미터가 CancellableContinuation이며 취소와 resume이 가능한 객체다
일단 어떻게 사용하는지 코드를 보자.
class TestViewModel : ViewModel() {
...
// 네트워크 요청시 받는 Callback 형태
interface NetworkResult {
fun success(resultCode: Int)
fun fail(cause: Throwable)
}
private var networkJob: Job? = null
// 네트워크 접속 요청시 결과를 callback으로 넘겨주는 함수 (Dummy)
private fun requestNetwork(resultCallback: NetworkResult) {
networkJob = viewModelScope.launch {
delay(500) //임의로 5초를 대기후 결과를 반환해 준다.
resultCallback.success(200)
}
}
...
}
위 코드는 1초 이내에 결과가 나오길 기대하는 기도메타 같은 코드이다. 모든 상황에서 1초이내에 결과가 나올것인가? 아무리봐도 아니다.
이런 경우에 콜백 처리를 할 수 있도록 코루틴을 지연시킬 수 있는 처리를 suspendCancellableCoroutine이 지원해 준다. 아래 코드와 같다
suspend fun suspendCoroutineTest(): Int {
val id = 155
val result = suspendCancellableCoroutine<Int> { continuation ->
TMDBService.service.getMovieDetail(id.toString()).enqueue(object : Callback<ResponseMovie> {
override fun onResponse(call: Call<ResponseMovie>, response: Response<ResponseMovie>) {
response.let {
if (it.isSuccessful) {
continuation.resume(2)
}
}
}
override fun onFailure(call: Call<ResponseMovie>, t: Throwable) {
}
})
}
return result
}
suspendCancellableCoroutine은 블록내의 코드를 모두 수행한 후 해당 블록을 blocking 상태로 둔다. 그리고 resume / resumeWithException이 호출 되고 나서야 그 값을 블록 밖으로 전달하고, 재개된다.
suspendCancellableCoroutine은 cancel이 가능하기 때문에(suspendCoroutine 도 있는데 이것은 cancel이 불가능하고, 나머지는 동일함) 취소에 대한 처리가 필요하다.
취소 처리는 두가지 방법이 있다.
1. invokeOnCancellation 블록을 선언해 주거나
2. resume의 람다로 cancel시의 액션을 정의해 주는 방법
continuation.invokeOnCancellation {
// 취소시 해야할 처리를 정의한다.
}
해당 취소 블록들은 job이 취소될 때에만 동작하며, 성공적으로 resume되면 동작하지 않는다. 또한 resume의 람다와 invokeOnCancellation 둘다 선언해 놓은경우 cancel되면 둘다 동작하게 된다.