OHJEHO
OHJEHO
Nov 11, 2018 · 11 min read

Kotlin Coroutine 튜토리얼

2018년 10월 29일 Kotlin 1.3이 정식 릴리즈 되면서, 몇가지 새로운 기능이 추가되었는데, 이 중에서 가장 화제가 된 부분은 코루틴이다.

Coroutine?

코루틴 이라는 단어는 어디서 나온 단어인가? 이를 알기 위해서는 우선 서브루틴이라는 개념을 알아야 한다. 서브루틴은 흔히 말하는 함수와 같다. 서브루틴과 함수는 특정 로직을 호출하고 이것이 완료되어야만 다음 단계로 넘어가야 한다는 점에서는 같지만 한 가지 차이점이 있다. 함수는 일반적으로 값을 CPU의 레지스터를 통해 반환하지만 서브루틴은 반환하지 않는다. 다시 말해서, 함수는 서브루틴을 좀 더 일반화 시킨 개념이라고 볼 수도 있다.

함수는 시작되는 지점과 끝나는 지점이 한 곳으로 정해져 있다. 다음 getSum 함수를 보면 getSum() 함수를 호출하며 매개변수로 정수 a, b를 전달한다. 이후 함수 내부에서 두 매개변수를 더한 후 더한 값을 반환한다. 일반적으로 메인 스레드에서 이 함수를 호출하면 내부 로직이 끝날 때 까지 메인 스레드에서 다음 코드의 동작이 중지되며 함수의 실행이 다른 외부 요인에 의해 중지되지 않는다.

private fun getSum(a : Int, b : Int) : Int{
val result = a + b
return result
}

코루틴은 서브루틴의 확정된 개념이라고 볼 수 있다. 함수와는 다르게 시작과 끝이 아닌 로직 중간의 어느 부분에서라도 시작과 종료가 이루어질 수 있으며 실행을 일시중지하고 다른 코루틴으로 이동할 수도 있다. 간단한 문법으로 비동기 태스크를 처리할 수 있으며 기존의 스레드를 사용하는 것보다 훨씬 적은 자원을 소비한다. JetBrains에 따르면 코루틴은 일종의 저비용 스레드라고 한다.그렇다면, 어쨌든 스레드가 아닌가? 무슨 차이가 있는걸까?

Thread vs Coroutine

Thread

  • OS의 Native Thread에 직접 링크되어 동작하여 많은 시스템 자원을 사용한다.
  • Thread간 전환 시에도 CPU의 상태 체크가 필요하므로 그만큼의 비용이 발생한다.

Coroutine

  • 코루틴은 즉시 실행하는 게 아니며, Thread와 다르게 OS의 영향을 받지 않아 그만큼 비용이 들어가지 않는다.
  • 코루틴 전환시 Context Switch가 일어나지 않는다.
  • 개발자가 직접 루틴을 언제 실행할지, 언제 종료할지 모두 지정이 가능하다.
  • 이렇게 생성한 루틴은 작업 전환 시에 시스템의 영향을 받지 않아 그에 따른 비용이 발생하지 않는다.

스레드와 코틀린의 차이 링크 : Difference between thread and coroutine in Kotlin

Android 프로젝트에서 코루틴 사용

코틀린 버전을 1.3으로 마이그레이션 하고 다음 라이브러리를 app/gradle 디펜던시에 추가해준다.

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'

기본 코루틴 문법

코틀린 1.3 코루틴 공식 가이드 문서

다음의 두 코드는 완벽하게 동일한 태스크를 수행한다.

Thread(Runnable {
for(i in 1 .. 10) {
Thread.sleep(1000L)
println("I'm working")
}
}).start()
GlobalScope.launch(Dispatchers.Default) {
repeat(10){
delay(1000L)
println("I'm working")
}
}

1초에 한번씩 총 열번 문자열을 백그라운드 스레드에서 출력하는 샘플 코드이다. repeat() 및 delay() 함수는 코루틴에서 제공하는 함수이다.

위의 스레드 관련 코드는 코틀린으로 작성하였고, 간단한 동작이므로 소스코드의 복잡도나 가독성 면에서 큰 차이가 없어보인다.

코루틴 cancel()

Thread를 생성 후 마음대로 stop 시키는 것이 죄악으로 취급받는 반면에 코루틴은 실행 도중 얼마든지 프로그래머 마음대로 취소시킬 수 있다. 다음 코드는 코루틴 실행 후 3000ms 뒤에 해당 코루틴을 취소시키는 코드이다.

val job = GlobalScope.launch(Dispatchers.Default) {
repeat(10) {
delay(1000L)
println("I'm working")
}
}
runBlocking {
delay(3000L)
job.cancel()
println("Coroutine is done..!")
}

Thread와 마찬가지고 join()을 통해 해당 코루틴이 종료될때 까지 호출한 스레드를 블록 시킬 수 있다.

val job = GlobalScope.launch(Dispatchers.Default) {
repeat(10) {
delay(1000L)
println("I'm working")
}
}
runBlocking {
delay(1000L)
job.join()
}

cancelAndJoin 메소드를 통해서 취소 후 코루틴의 종료를 기다릴 수도 있다.

val job = GlobalScope.launch(Dispatchers.Default) {
repeat(10){
delay(1000L)
println("I'm working")
}
}
runBlocking {
job.cancelAndJoin()
println("Coroutine is done..!")
}

코루틴 Timeout

withTimeout() 메소드를 통해 해당 코루틴이 최대 실행시간을 정해줄 수 있다. 아래 코드에서는 4.2초가 지날때까지 코루틴의 태스크가 종료되지 않으면 바로 죵료시키게 된다. withTimeoutOrNull 메소드를 통해 타임아웃이 될 경우 Null을 발생시킬 수도 있다.

val job = GlobalScope.launch(Dispatchers.Default) {
withTimeout(4200L) {}
repeat(10) {
delay(1000L)
println("I'm working")
}
}

코루틴의 pause() / resume()

RX JAVA에서의 스케줄러와 같은 Dispatcher를 통해서 코루틴의 일시중지 / 재시작을 구현할 수 있다.

아래 코드는 CoroutineDispatcher를 상속받아 구현한 PauseableDispatcher 클래스이다.

class PauseableDispatcher(private val handler: Handler): CoroutineDispatcher() {
private val queue: Queue<Runnable> = LinkedList()
private var isPaused: Boolean = false

@Synchronized override fun dispatch(context: CoroutineContext, block: Runnable) {
if (isPaused) {
queue.add(block)
} else {
handler.post(block)
}
}

@Synchronized fun pause() {
isPaused = true
}

@Synchronized fun resume() {
isPaused = false
runQueue()
}

private fun runQueue() {
queue.iterator().let {
while (it.hasNext()) {
val block = it.next()
it.remove()
handler.post(block)
}
}
}
}

아래와 같이 사용한다. 300ms마다 문장을 100번 출력하는 코루틴을 시작 후 3000ms 후에 일시정지시킨 후 다시 3000ms 후에 재개시키는 코드이다.다 suspendFunc 함수 앞의 suspend 키워드는 코루틴에 의해서 언제든 지연되었다가 재개 될 수 있는 함수임을 의미하며, 코루틴 블록 내에서만 호출할 수 있다.

private val dispatcher = PauseableDispatcher(Handler(Looper.getMainLooper()))fun startPauseableCoroutine() {
val job = GlobalScope.launch(dispatcher) {
if (this.isActive) {
suspendFunc()
}
}
GlobalScope.launch {
println("Start Coroutine...")
delay(3000L)
dispatcher.pause()
println("Pause Coroutine...")
delay(3000L)
dispatcher.resume()
println("Resume Coroutine...")
delay(3000L)
job.cancelAndJoin()
println("Cancel Coroutine...")
}
}
suspend fun suspendFunc() {
repeat(100) {
delay(300)
println("I'm working... count : $it")
}
}

다음을 실행하면 다음과 같은 결과가 나오개 된다.

I/System.out: I’m working… count : 1
I/System.out: I’m working… count : 2
I/System.out: I’m working… count : 3
I/System.out: I’m working… count : 4
I/System.out: I’m working… count : 5
I/System.out: I’m working… count : 6
I/System.out: I’m working… count : 7
I/System.out: Pause Coroutine…
I/System.out: Resume Coroutine…
I/System.out: I’m working… count : 8
I/System.out: I’m working… count : 9
I/System.out: I’m working… count : 10
I/System.out: I’m working… count : 11
I/System.out: I’m working… count : 12
I/System.out: I’m working… count : 13
I/System.out: I’m working… count : 14
I/System.out: I’m working… count : 15
I/System.out: I’m working… count : 16
I/System.out: I’m working… count : 17
I/System.out: Cancel Coroutine…

count가 7이 되는 시점에서 해당 suspend 함수가 일시중지 되고 3000ms 후 해당 함수가 재개되었다. 단순하게 코루틴이 정지 후 재시작 하는것이 아니기 때문에 코루틴 컨텍스트는 보존되고 있고 그에 따라서 count는 8부터 출력된다.

아직 코루틴의 많은 부분을 다뤄 보지는 않았지만, 입문 난이도는 RX JAVA보다 훨씬 쉬워 보인다. RX JAVA처럼 태스크 생성 및 구독에 대한 모든 문법과 그것이 의미하는 것을 알고있지 않아도 간단하게 비동기 태스크 작성이 가능하다. 다만 ….

코루틴과 RX JAVA의 러닝커브 비교

어쨌든 기술을 계속 활용하면서 정점으로 가다보면 어느 순간에는 비슷한가 보다 아직 셀렉터와 채널을 다루어보지도 못했다. 중요한 점은, 기존 스레드가 하던 역활을 완벽하게 대체할수 있을 것으로 보인다는 것과 비동기 태스크의 생명주기를 개발자 마음대로 컨트롤 할 수 있다는 것 만으로도 큰 매력으로 다가오지 않을까 한다는 점이다.

Reference

코틀린 코루틴 공식 문서

Kotlin의 코루틴은 어떻게 동작하는가

RxJava와 Kotlin Coroutines 비교해보기

Pause a job

Difference between thread and coroutine in Kotlin

Difference between subroutine, co-routine, function and thread?

OHJEHO

Written by

OHJEHO

Android, JAVA, Kotlin Developer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade