Coroutine에 대해서 지난 번 포스팅 글보다 조금 더 상세하게 다시 정리해보려고 한다. 사용을 하다 보니 지난 번에 작성했던 것보다 더 디테일한 부분들도 있고 어떻게 동작하는 지도 더 알면 좋을 것 같아서이다.
Coroutine에 대해 다시 한번 생각해보면, 이를 사용해서 callback의 사용을 줄이면서 더 한눈에 들어오게 사용할 수 있다는 장점이 있다. 다른 프로그래밍 언어(ex. javascript)에서의 async await라고 생각할 수도 있다. 비동기 처리 패턴 중의 하나인데, 기존의 비동기 처리 방식인 callback의 단점을 보완하여 더 깔끔하게 코드를 작성할 수 있다.
Kotlin은 suspend 키워드를 사용해서 여러 라이브러리 함수와 함께 비동기 프로그래밍을 할 수 있도록 해준다.
Coroutine
Kotlin 개발자들은 Coroutine을 "lightweight threads"라고 정의했다. Coroutine은 실제 쓰레드가 실행할 수 있는 일종의 작업들이다.
위의 그림과 같이 작업들이 막히지 않고 여러 쓰레드를 통해 계속 진행되도록 사용할 수 있다. 그렇게 하는 부분에 있어서 복잡한 구현과 코드도 필요하지 않고, 코드도 깔끔하게 작성할 수 있다.
쓰레드는 suspended 되는 특정 지점에서 Coroutine의 실행을 중지하고 다른 작업을 진행할 수 있다. 그렇기에 작업이 막혀서 진행이 멈추는 일이 발생하지 않는다. suspended 되었던 부분은 나중에 다시 진행하거나, 다른 쓰레드가 작업을 이어서 진행할 수도 있다.
하나의 Coroutine은 하나의 작업을 의미하는 것이 아니다. 보장된 특정 순서대로 실행되는 일련의 하위 작업들로 생각하는 것이 더 맞다. 코드가 한 블럭 안에 순차적으로 있는 것처럼 보이더라도 suspend 함수에 대한 각 호출은 Coroutine 내에서 새로운 하위 작업의 시작 블럭을 만들어 준다.
Suspend Function
위와 같이 함수 선언부 앞에 "suspend" 키워드만 붙여 주면 suspend function을 만들 수 있다. suspend function은 진행 중인 쓰레드의 blocking 없이 진행 중이던 Coroutine을 일시 중단할 수 있다. 이는 suspend function을 호출할 때 해당 코드가 일시 중지될 수는 있지만, 쓰레드가 멈추거나 하지는 않는다는 뜻이다. 이렇게 Coroutine이 중지되면, 다른 중지되어 있던 Coroutine 작업이 진행될 수 있다. 이렇게만 보면 suspend function이 본질적으로 비동기 방식으로 진행되는 것은 없다고 볼 수 있다.
Suspend function은 명시적으로 사용되는 경우에만 비동기식으로 동작하기 때문이다.
이러한 부분에 대해서는 좀 더 밑에서 확인해볼 것이다. 지금은 시간이 걸리는 특수한 함수를 suspend function으로 표시한다고 생각할 수 있다. suspend 키워드만을 이용하여 쓰레드와 디스패치에 대해 고민과 걱정을 하지 않고 함수를 하위 작업으로 나눠줄 수 있기에 편리한 것이다. 실제로, suspend 함수를 작성할 때는 함수 내부에서 이러한 것들을 고려하지 않고 일반적으로 코드를 작성하는 것과 동일하게 작성하면 된다.
Suspend Function - 순차적 작성
suspend function은 suspend 키워드를 제외하고, Rx처럼 특별한 반환 타입이나 형태를 가지지 않는 것을 알 수 있다. 완전히 일반적인 함수처럼 작성할 수 있는 것이다. suspend function에서는 코드가 순차적으로 실행된다.
suspend function에서 다른 suspend function을 호출하는 부분을 보면, 일반적인 함수와 다를 것이 없다. 호출한 함수가 결과를 반환할 때까지 기다렸다가, 해당 부분이 끝나면 다음 줄부터 코드가 실행된다. 이렇게 일반적인 함수와 똑같이 사용할 수 있기 때문에 복잡한 비동기 코드를 간단하게 작성하여 사용할 수 있는 것이다.
Coroutine Builder
사실 일반 함수에서 suspend function을 직접 호출하면 컴파일 에러가 발생한다. Coroutine에서만 일시 중지될 수 있기 때문이다. 그렇기에 suspend function을 실행하기 위해서 Coroutine을 생성해야 한다는 것을 알 수 있다. Coroutine Builder를 이용하여 Coroutine을 생성할 수 있다.
Coroutine Builder는 suspend function을 실행하기 위한 새로운 Coroutine을 만드는 간단한 함수이다. Coroutine은 스스로 중단되지 않기 때문에 일반적인 함수에서 호출할 수 있고, 이를 통해 일반적인 함수와 suspend 함수 사이의 다리 역할을 해준다. Kotlin의 표준 라이브러리는 다양한 작업을 위한 여러 코루틴 빌더가 포함되어 있다.
Coroutine Builder - runBlocking
runBlocking을 이용하여 현재 쓰레드를 blocking할 수 있다. 일반적인 함수에서 suspend function을 다루는 가장 간단한 방법은 현재 쓰레드를 blocking 하고 기다리는 것이다. 이렇게 사용할 수 있는 Coroutine Builder는 runBlocking이다.
runBlocking을 통해 suspend lambda를 Coroutine에서 실행한다. runBlocking Coroutine의 실행과 동시에 메인 쓰레드는 Coroutine이 끝날 때까지 block된다. 위의 코드를 실행하면 첫 문구가 출력되고 2초 뒤에 다음 문구가 출력되는 것을 확인할 수 있다.
runBlocking에 전달하는 block이 suspend function이 아니더라도, suspend function 형태로 전달되어 동작한다.
Coroutine Builder - launch
일반적으로 Coroutine을 사용하는 경우는 앞과 같이 쓰레드를 blocking 할 때가 아니라, 비동기 작업을 시작할 때이다. launch라는 Coroutine Builder를 사용하면 백그라운드에서 Coroutine 작업을 수행할 수 있다.
Kotlin Documentation에 있는 예시이다. 이것을 실행해보면 "Hello,"가 먼저 출력되고, 1초 뒤에 "World!"가 출력되는 것을 확인할 수 있다. runBlocking은 동작 확인을 위해 메인 쓰레드를 붙잡으려고 추가한 부분이고, launch를 확인해보면 된다. Global Scope는 다음 글에서 다시 다루겠다.
Coroutine Builder - async
값을 반환하는 비동기 작업이 필요할 때는 async라는 Coroutine Builder를 사용할 수 있다.
지연된 반환 값을 받기 위해, async는 Deffered 객체를 반환한다. 이 객체의 await()를 호출하면 결과 값을 받을 때까지 기다릴 수 있다. await()는 일반적인 blocking 함수가 아닌, suspend function이어서 일반 함수에서 바로 호출할 수 없다. 그래서 위의 예시에서는 runBlocking을 통해 한번 감싸주었다.
'Android > Kotlin' 카테고리의 다른 글
Kotlin - Coroutine 추가 정리 3 (258) | 2020.05.31 |
---|---|
Kotlin - Coroutine 추가 정리 2 (355) | 2020.05.29 |
Kotlin : local, infix, inline functions, Operator Overloading (613) | 2020.05.25 |
Kotlin - Sealed Class (609) | 2020.05.21 |
Kotlin : run, with, let, also and apply (4) | 2020.05.17 |
댓글