봄날은 갔다. 이제 그 정신으로 공부하자

Coroutine - part2 본문

android Tip

Coroutine - part2

길재의 그 정신으로 공부하자 2020. 12. 28. 13:17

이전 글

als2019.tistory.com/35

참고 사이트: 

https://developer.android.com/kotlin/coroutines?hl=ko

 

Android의 Kotlin 코루틴  |  Android 개발자  |  Android Developers

코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다. 코루틴은 Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확립된 개념을 기반으로

developer.android.com

 

작업 Timeout 처리하기 

비동기 처리의 단점은 비동기 작업을 취소하는데 많은 리소스가 소모된다는데 있습니다. 

코루틴에서는 작업 취소를 위해 Job의 cancel() 함수외에도 timeout()함수를 지원합니다.

timeout()함수는 launch{ … }블록이 실행되다가 정해진 시간 이상을 사용한 경우, 해당 블록을 종료해주는 함수이며 사용법은 아래와 같습니다.

Log.d(TAG, "Main Thread - Start")
runBlocking {
    val job1 = launch {
        withTimeout(500L) {
            repeat(100) {
                Log.d(TAG, "launch #${it}")
                delay(100L)
            }
        }
    }

    delay(2000L)
    job1.cancelAndJoin()
    Log.d(TAG, "end Block")
}
Log.d(TAG, "Main Thread - End")

 

위 코드는 launch{} 블록이 100번 실행되는 조건 이지만 2sec후에 cancelAndJoin()함수로 인해 launch{} 블록 종료되어야 합니다.

하지만 withTimeout() 함수로 인해 2sec후에 cancelAndJoin() 동작하기 전에 먼저 withTimeout()이 동작하고 작업은 끝나게 됩니다.

출력되는 결과는 아래와 같습니다.

>> Main Thread - Start

>> launch #0

>> launch #1

>> launch #2

>> launch #3

>> launch #4

>> end Block

>> Main Thread - End

 

launch{...} 블록내 비동기 작업 결과 받기

비동기적 작업이 launch{…} 으로 구분되서 작업되면 좋겠지만 launch{…}블록 내부에서도 비동기 작업이 필요한 경우가 있고 또 블록 내에서 그 결과를 받아서 처리해야 하는 경우가 발생하기도 합니다.

이러한 경우에는 Job을 사용한 처리가 불가능하기 때문에 다른 방법을 사용해야 합니다.

코루틴에서는 async{} 블록을 통해 비동기 작업의 결과를 받아서 처리할 수 있도록 지원합니다.

 

아래 코드는 GlobalScope.launch{} 블록이 실행되어Result Value is”를 출력 한 후에 async 블록에 의해 비동기 작업이 실행되고 Thread는 Log.d(TAG, "${value.await()}”)에서 대기하게 됩니다.

async{} 블록의 작업이 완료되면 value.await()가 호출되게 되고 멈추어 있던 Thread가 깨어나면서 값을 출력하게 됩니다.

GlobalScope.launch {
    Log.d(TAG, "Result Value is ")
    val value = async {
        var total = 0
        for(i in 0 ..10){
            total += i
            delay(200L)
        }
        total
    }
    Log.d(TAG, "${value.await()}")
}

 

async{} 블록은 기본적으로 바로 실행되지만 CoroutineStart.LAZY를 사용하여 원하는 시점에 실행되도록 처리할 수 있습니다.

아래와 같이 처리하게 되면 두 번째 async{} 블록이 먼저 실행되게 됩니다.

GlobalScope.launch {
    val value1 = async(start = CoroutineStart.LAZY) {
        Log.d(TAG, "async #1")
        100
    }

    val value2 = async(start = CoroutineStart.LAZY) {
        Log.d(TAG, "async #2")
        200
    }

    value2.start()
    value1.start()
    Log.d(TAG, "${value1.await()}")
    Log.d(TAG, "${value2.await()}")
}

 

Channel을 사용해 데이터 전달받기

코루틴은 채널(Channel)이라는 개념을 통해 코루틴에서 생성한 데이터를 또 다른 코루틴에게 전달할 수 있습니다. 

아래의 코드는 채널을 사용하여 코루틴 launch{} 블록간 데이터를 공유하는 예제 입니다.

Log.d(TAG, "Main Thread - Start")
runBlocking {
    val channel = Channel<Int>()

    launch {
        for(i in 0 ..4){
            delay(1000L)
            channel.send(i)
        }
        channel.close()
    }

    for(ch in channel){
        Log.d(TAG, "channel value: ${ch}")
    }
}
Log.d(TAG, "Main Thread - End")

 

코루틴에서 함수 사용하기

코루틴 블록 내 작업이 많아지는 경우, 이를 별도의 함수로 분리하게 되는데 코루틴 내부에서 실행되는 함수는 suspend로 지정해야 합니다.

아래는 비동기적으로 실행된 코루틴이 완료되어 그 결과를 반환받는 예제 입니다.

suspend 처리되어 있지 않으면 doSomething() 함수에서 await()함수 호출 시 빌드 에러가 발생합니다.

runBlocking {
    val value = doSomething()
    Log.d(TAG, "${value}")
}

suspend fun doSomething(): Int {
    Log.d(TAG, "doSomething")
    val value = GlobalScope.async(Dispatchers.IO) {
        var total = 0
        for(i in 0..10){
            total += i
        }
        total
    }
    return value.await()
}

 

Comments