본문 바로가기

Coroutine

Coroutines - Part. 5 안드로이드에서의 코루틴 - 동기식과 비동기식, 콜백과 코루틴

728x90

동기식 코루틴 (synchronous : 순차적인 실행)

동기식의 코루틴을 실행할 필요가 있을 시 비동기식 코루틴 빌더에 코루틴의 start() 함수를 정의함으로써 동기식으로 사용이 가능하다.

 

uiScope.launch(handler) {
            val job1 = async(Dispatchers.Default, CoroutineStart.LAZY) {
                //백그라운드 스레드에서 동작
                Log.v("job 1", "작업 중!!")
                delay(3000)
            }
            val job2 = async(Dispatchers.Default, CoroutineStart.LAZY) {
                //백그라운드 스레드에서 동작
                Log.v("job 2", "작업 중!!")
                delay(3000)
            }
            
            Log.v("job1, 2", "준비")
            //job1.start()
            //job2.start()
            Log.v("job1, 2", "실행")
            //UI 스레드에서 동작
            Log.v("DATA", "Output "+job1.await() + "  " + job2.await())
        }

실행결과:

2021-01-23 17:53:49.956  V/job1, 2: 준비
2021-01-23 17:53:49.956  V/job1, 2: 실행
2021-01-23 17:53:49.968  V/job 1: 작업 중!!
... (3초뒤)
2021-01-23 17:53:52.973  V/job 2: 작업 중!!
... (3초뒤)
2021-01-23 17:53:55.974  V/DATA: Output kotlin.Unit  kotlin.Unit

 

위 코드는 await() 함수가 호출되면 job1과 job2 코루틴 블럭이 실행되며, 실행결과를 보면 49초에 job1의 log가 출력이 되고 52초에 job2의 log가 출력되고 55초에 job1, 2에 대한 결과를 확인할 수 있다. 이유는 job1 코루틴 블럭이 완료될 때 까지 두번째 코루틴이 시작되지 않기 때문인데, start() 함수를 호출하여 코루틴이 비동기(병렬)로 실행될 수 있도록 할 수 있다.

 

비동기식 코루틴 (Asynchronous : 병렬적인 실행)

uiScope.launch(handler) {
            val job1 = async(Dispatchers.Default, CoroutineStart.LAZY) {
                //백그라운드 스레드에서 동작
                Log.v("job 1", "작업 중!!")
                delay(3000)
            }
            val job2 = async(Dispatchers.Default, CoroutineStart.LAZY) {
                //백그라운드 스레드에서 동작
                Log.v("job 2", "작업 중!!")
                delay(3000)
            }

            Log.v("job1, 2", "준비")
            job1.start()
            job2.start()
            Log.v("job1, 2", "실행")
            //UI 스레드에서 동작
            Log.v("DATA", "Output "+job1.await() + "  " + job2.await())
        }

실행결과:

2021-01-23 18:14:12.493 16467-16467/com.example.coroutine_study V/job1, 2: 준비
2021-01-23 18:14:12.505 16467-16501/com.example.coroutine_study V/job 1: 작업 중!!
2021-01-23 18:14:12.506 16467-16502/com.example.coroutine_study V/job 2: 작업 중!!
2021-01-23 18:14:12.506 16467-16467/com.example.coroutine_study V/job1, 2: 실행
... (3초뒤)
2021-01-23 18:14:15.512 16467-16467/com.example.coroutine_study V/DATA: Output kotlin.Unit  kotlin.Unit

 

위 코드는 start() 함수를 사용한 코드이다. 실행결과를 보면 12초에 job1과 job2의 log가 출력이 되고 15초에 job1, 2에 대한 결과를 확인할 수 있다.

start() 함수를 사용하지 않고 await() 호출시점에서 코루틴 블럭들이 실행되고 작업을 기다린 후 출력되게 하는 방법도 있다. awaitAll() 함수를 이용하면 된다.

결론적으로 말하면 CPU에서 여러 개의 task(CPU에게 의뢰하는 작업 단위)를 작업하기 위해 여러 개의 스레드가 병행적으로 실행되는 것처럼 각각의 코루틴 블럭들이 병행 처리되는 것은 아니다.

구조적 동시성을 통하여 하나의 스레드를 이용하면서 코루틴 블럭을 옮겨가며 처리하는 것이다.

콜백과 코루틴

코루틴 블럭을 실행한 결과를 그 자리에서 반환받지 않고 콜백을 이용해 값을 반환받고 싶을 땐 suspendCoroutine 빌더를 이용하면 된다.

 

class MainViewModel : ViewModel() {
    private val job = Job()
    private val uiScope = CoroutineScope(Dispatchers.Main + job)
    private val handler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.e("Exception :", "예외 상황 - " + throwable)
    }

    fun doSomeOperation() {
        uiScope.launch(handler) {
            Log.v("코루틴 블럭","작업 중")
            delay(1000)

            val result = getFromCallback()
            Log.v("Res",""+result)

            Log.v("코루틴 블럭","작업 중")
            delay(1000)
        }
    }
    private suspend fun getFromCallback() = suspendCoroutine<Int> {

        Handler().postDelayed(object : Runnable {
            override fun run() {
                //결과를 즉시 반환합니다
                //it.resume(15)

                //또는, Result success 로 결과를 감싸서 반환합니다
                it.resumeWith(Result.success(15))

                //또는, Result failure 로 결과를 감싸서 반환합니다
                //it.resumeWith(Result.failure(AssertionError()))
            }
        }, 5000)
    }
    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}