Antilog의 개발로 쓰다
반응형

서론

사실 Coroutine 을 처음 접하고 학습한 것은 2020년에서 2021년 사이였다. 당시에는 단순한 학습을 하고 사용할 기회만 있었을 뿐, 깊이 이해하고 다양하게 활용할 기회가 없었다.

최근 근무하며 다양한 Coroutine 코드를 접하고 직접 테스트 코드를 작성해본 경험을 바탕으로, 앞으로 만들어갈 Mulitplatform Project에서 Coroutine 을 더 잘 활용하기 위해, 과거에 알던 내용에 새로 생긴 궁금증을 해결하며 다시 한번 Coroutine 을 복습하고 다시 정리해보고자 한다.

들어가기에 앞서...

처음 비동기 작업을 위해 학습을 시작하던 때가 Android 11에서 12로 넘어가던 시절이던 것 같다.

Coroutine 에 대해 자세하게 작성해보기 전에 왜 어쩌다가 Coroutine 을 학습하고 적용하게 되었는지 작성해보려한다.

비동기 작업의 필요성

사실 첫 학습에서 안드로이드는 동기적으로 오래 걸리는 작업을 하면 반응성이 떨어지고, 이런 상황에서 Application Not Responding(ANR) 이 발생하기에 이런 작업을 따로 해야 한다고 학습했다. 당시에는 아 UI 도 그리고 이벤트도 처리하니까 이 상황에 다운로드도 하면 반응성이 떨어지는구나 했던 것 같다...

안드로이드 어플리케이션이 실행되면 Android System은 ActivityThread를 통해 하나의 단일 Thread 로 Process 를 시작한다. 기본적으로 동일한 어플리케이션 내 Components 는 같은 process 와 Thread 에서 실행되기 때문에 이때 이 Thread 를 Main Thread 라고 한다.
이 과정을 자세하게 이야기 하면 ZygoteInit 부터 시작되겠지만 결국 시작점인 main 함수와 같은 역할로 볼 수 있는 곳이 ActivityThread 에 main 이기 때문에 이와 같이 설명했다.
또한 ActivityThread 라는 이름을 가졌기에 Activity 만을 위한 것이 아닌 모든 Components 와 관련되어있다.

또한 이렇게 만들어진 Main Thread 는 화면 구성에 대한 역할을 한다.
더불어 Android에서 UI toolkit(Button, CheckBox, etc...)은 Thread Safty 하지 않아 반드시 Main Thread 에서만 조작되어야 한다. 따라서 이를 UI Thread 라고도 부른다. 따라서 UI 동작은 Single Thread 구조로 동작한다.
대부분 GUI Application 에서 UI에 해당하는 컴포넌트는 UI Thread 외에서 접근할 수 없다. 서로 다른 Thread 에서 draw 를 하는 경우 경합 상태가 발생하며 결과를 에측할 수 없게 된다. 다양한 노력이 있었으나 Thread safty 하게 하는 것이 이득보다 잃는 것이 많아 Single Thread 형식으로 되었다고 한다. 자세한 내용은 이 곳에서 확인하면 좋을 것 같다.

그렇기에 만약 오래걸리는 API 통신이나 다운로드 작업을 이 MainThread 라고 하는 곳에서 하게 된다면, 당연하게도 이 MainThread 가 block 되는 샘이며 결과적으로는 UI 반응성이 느려지거나 아예 멈추어 말 그대로인 Application Not Responding 이 되는 것이다.

따라서 이러한 작업을 수행하기 위해서는 반드시 별도의 Thread 에서 작업을 해야한다.

Coroutine 을 학습하고 적용하게된 이유

처음 안드로이드를 학습하던 Android 11 에서 12 로 넘어가던 시절에는 무려 지금은 보이지도 않고 아는 사람도 줄어드는 AsyncTask 가 Deprecated 된지 1년 겨우 지난 시점이었다.
따라서 당시에는 이 비동기 처리를 위해서 아주 많은 예제가 존재했다.
물론 지금은 Coroutine 아니면 레거시로 남은 RxJava 정도지만 당시에는 오히려 Rxjava 가 필수요소였다...

직접 Thread 를 생성하고 만들어 비동기 처리

교과서적인 방법으로는 다중 스레딩 방식이 기본이었다. Process Context Switching 보다 CPU 캐시 메모리 초기화 과정이 없고, PCB 보다 TCB 에 저장되는 정보가 적어 가볍다고는 하지만 고려해야할 부분이 매우 많았다.

소프트웨어적 Thread 는 아무리 많이 만들 수 있다고는 하지만, 결국 하드웨어적 Thread는 정해져있고 시작 가능한 Thead 는 OS 에 의해 제한되며 Thread 자체가 비용이 저렴한 요소가 아니었기에 Thread Pool 을 이용해야했다.

그럼에도 동시성 제어와 같은 고려할 점이 너무나도 많았다.

Handler & Looper 혹은 AsyncTask를 이용한 비동기 처리 방식

Handler & Looper 방식을 이용해도 오히려 Main Thread 가 아닌 곳에서는 UI 를 업데이트 할 수 없기 때문에 이를 고려해야했다.
반대로 AsyncTask 는 UI Thread 에서만 호출해야하기도 했다.

실제로 AsyncTask 는 시스템상의 결함들로 인해 Deprecated 되었다.

위 두 방식에 추가적인 문제점

위 방식들을 이용하다보면 결국 Callback 을 사용하게 된다.

위 방식이 아니라도 대표적으로 많은 사람이 아는 Retrofit2에서 지원하는 Callback 역시 같은 문제를 가지고 있었다.
Retrofit2 는 자체적으로 Thread 와 Thread Pool 를 OkHttp3의 Dispatcher 에서 관리하고 제공한다.
Callback 을 사용하면 중첩 Callback 도 발생하고 코드를 읽기 매우 어려워 코드 가독성이 낮아진다.
또한 사용하기 따라 Anonymous Class 를 사용하게 되며 Memory Leak 에 노출될 가능성이 높아지기도 하고 Boiler Plate Code 가 생기기도 한다.
실제로 이러한 문제를 해결하고자 Retrofit2 에서 Callback 객체의 Boiler Plate Code 는 없애고 사용성은 높이고자 다양한 방법으로 유틸성 함수와 객체를 만든 경험이 있다...

Reactive Extensions (Rxjava)

관찰 가능한 스트림을 사용하여 비동기적 작업을 지원해준다. 실제로 활용도도 매우 높아서 당시에는 Rx로 작성된 공식 샘플 예제도 있었다.

그러나 Rxjava 하나를 이해하기 위한 러닝 커브가 매우 높았고, 이를 위해서는 Observer Pattern 은 기본적으로 이해하고 있어야만 하는 정도였다.

그래서 결국 Coroutine 으로

개인적으로 이런 다양한 문제를 해결하고, Rxjava 보다 러닝커브가 낮고 또 읽기 편한 코드를 제공하는 Coroutine 을 결국 사용하게 된게 아닌가 싶다.

다른 수단보다 사용만을 위해서 쉽게 학습할 수 있으며 실제로 동기 코드 처럼 비동기 코드를 만들기 때문에 콜백 구조가 없고 코드 이해부터 디버깅까지 큰 장점으로 다가왔다.

또한 당시에는 앞으로는 Coroutine 이 대세가 될 것 이라는 말이 있었고 많은 기업에서 Coroutine 을 사용하는 움직임이 많았기에 더욱 Coroutine 을 선택하고 학습했던 것 같다.

마무리하며..

작성하고 보니 매우 길고, 주저리 주저리 이야기가 많아보이기도 한다. 하지만 당시 알아보고 넘어가 기록을 해두지 않아 휘발 되었던 부분을 복구한다는 느낌으로 작성하게 되었다.
당시에는 그저 알아보고 귀찮다는 이유로 정리하지 않아 현재의 스스로 고통받고 있으니... 미래의 나 자신이 또 현재와 유사한 이유로 필요로 한다면 또 다시 고통 받지 않게 하기 위해서 작성한 글이라고 보면 될 것 같다...

반응형

'Android > Coroutine' 카테고리의 다른 글

[Coroutine] Coroutine 이란?  (1) 2025.03.03
profile

Antilog의 개발로 쓰다

@Parker_J_S

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...