일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- android ar 개발
- 안드로이드 레트로핏 사용법
- 스택 큐 차이
- ANR이란
- 안드로이드 유닛테스트란
- 객체
- 안드로이드 os 구조
- 클래스
- 서비스 쓰레드 차이
- 안드로이드 유닛 테스트 예시
- rxjava hot observable
- Rxjava Observable
- jvm이란
- jvm 작동 원리
- 안드로이드 레트로핏 crud
- rxjava disposable
- android retrofit login
- 큐 자바 코드
- 2022 플러터 안드로이드 스튜디오
- 안드로이드 유닛 테스트
- ar vr 차이
- 안드로이드 라이선스
- 안드로이드 라이선스 종류
- 멤버변수
- 플러터 설치 2022
- 서비스 vs 쓰레드
- 자바 다형성
- 스택 자바 코드
- rxjava cold observable
- 2022 플러터 설치
- Today
- Total
나만을 위한 블로그
[코틀린 코루틴] 1. 코루틴을 배워야 하는 이유 본문
안드로이드에서 비동기적으로 연산하는 방법은 여럿 있다.
- RxJava, Reactor 등의 JVM 계열 라이브러리 사용
- 자바 자체적으로 지원하는 멀티 쓰레드
- 콜백 함수
그럼 왜 코루틴을 배워야 할까?
- 코루틴은 기존 방식들보다 많은 걸 지원한다
- 1963년에 처음 소개된 논문에서의 기능을 실생활에서도 유용하게 쓸 수 있도록 라이브러리로 만들어짐
- 코틀린을 쓰는 모든 플랫폼(JVM, JS, iOS, 다른 모듈들)에서 사용할 수 있다(멀티 플랫폼)
안드로이드에서의 코루틴 사용
프론트엔드에서 앱 로직을 구현할 때 가장 흔하게 쓰는 방법은
- 하나 or 다양한 소스(api, 뷰 컴포넌트, DB, 설정, 다른 앱)로부터 데이터를 가져옴
- 데이터 가공
- 가공된 데이터로 뭔가를 함(뷰에 표시, DB에 저장, api로 전송 등)
api로부터 뉴스를 가져와서 정렬한 다음 화면에 띄우는 로직을 구현하는 경우를 가정한다. 간단하게 구현한다면 아래처럼 될 수 있다.
fun onCreate() {
val news = getNewsFromApi()
val sortedNews = news.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
그러나 안드로이드에선 위처럼 구현할 수 없다. 하나의 앱에서 뷰를 다루는 쓰레드는 단 하나뿐이라 블로킹되면 안 되기 때문이다.
쓰레드 전환
쓰레드 전환이 위 문제를 푸는 가장 직관적인 방법이다. 블로킹 가능한 쓰레드를 먼저 쓰고 이후에 메인 쓰레드로 전환한다.
fun onCreate() {
thread {
val news = getNewsFromApi()
val sortedNews = news.sortedByDescending { it.publishedAt }
runOnUiThread {
view.showNews(sortedNews)
}
}
}
이 방식은 아래 문제가 있다.
- 쓰레드가 실행됐을 때 멈출 수 있는 방법이 없어서 메모리 누수로 이어질 수 있다
- 쓰레드를 많이 생성하면 비용이 많이 든다
- 쓰레드를 자주 전환하면 복잡도를 증가시키고 관리도 어렵다
- 코드가 쓸데없이 길어지고 이해가 어려워진다
뷰를 열었다가 바로 닫았다고 가정한다. 뷰가 열려 있는 동안 데이터를 가져와서 처리하는 쓰레드가 많이 생성될 것이다.
생성된 쓰레드들을 제거하지 않으면 쓰레드는 주어진 작업을 계속 수행한 후 더 이상 존재하지 않는 뷰를 수정하려고 시도할 것이다.
이것은 불필요한 작업이고 백그라운드에서 예외를 유발하거나 예상하기 어려운 결과가 나올 수 있다.
콜백
콜백의 기본적인 방법은 함수를 논블로킹으로 만들고 함수가 끝났을 때 호출될 콜백 함수를 넘겨주는 것이다.
fun onCreate() {
getNewsFromApi { news ->
val sortedNews = news.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
}
이 방식도 중간에 취소할 수 없다. 취소 가능한 콜백 함수를 구현할 수도 있지만 쉽지 않다. 콜백 함수 각각에 대해 취소할 수 있게 구현해야 하고 취소하려면 모든 객체를 분리해서 모아야 하기 때문이다.
3곳에서 데이터를 얻어오는 경우를 확인한다.
fun onCreate() {
getConfigFromApi { config ->
getNewsFromApi(config) { news ->
getUserFromApi { user ->
view.showNews(user, news)
}
}
}
}
이 방식은 아래 이유 때문에 완벽한 해결법이 될 수 없다.
- 뉴스를 얻어오는 작업, 유저 데이터를 얻어오는 작업은 병렬 처리할 수 있지만 현재의 콜백 구조로는 두 작업을 동시에 처리할 수 없다
- 취소할 수 있게 구현하려면 공수가 많이 든다
- 들여쓰기가 많아질수록 코드는 읽기 어려워진다 (콜백 지옥)
- 작업 순서를 다루기 힘들다
RxJava, 리액티브 스트림
자바 진영에서 인기 있는 RxJava나 Reactor 같은 리액티브 스트림을 쓰는 게 다른 해결법이다.
이 방법을 쓰면 데이터 스트림 안에서 일어나는 모든 연산을 시작, 처리, 관찰할 수 있다. 리액티브 스트림은 쓰레드 전환, 동시성 처리를 지원해서 앱 내의 연산을 병렬 처리하는 데 쓰인다.
fun onCreate() {
disposables += getNewsFromApi()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { news ->
news.sortedByDescending { it.publishedAt }
}
.subscribe { sortedNews ->
view.showNews(sortedNews)
}
}
RxJava가 콜백보다 더 좋은 방법이다. 메모리 누수도 없고 취소 가능하며 쓰레드를 적절하게 사용한다.
하지만 구현하기 복잡하단 단점이 있다. 처음 봤던 간단한 코드와 비교하면 완전히 다른 형태의 코드다. 그리고 객체를 리턴하는 함수들은 Observable, Single로 래핑해야 하고, 도입하려면 많은 코드를 바꿔야 한다.
코루틴 사용
코루틴이 도입한 핵심 기능은 코루틴을 특정 지점에서 멈추고 이후에 재개할 수 있는 것이다.
코루틴을 쓰면 코드를 메인 쓰레드에서 실행하고 api에서 데이터를 얻어올 때 잠깐 중단시킬 수도 있다. 코루틴을 중단시켰을 때 쓰레드는 블로킹되지 않으며, 뷰를 바꾸거나 다른 코루틴 실행 등의 다른 작업을 할 수 있다.
데이터가 준비되면 코루틴은 메인 쓰레드에서 대기하고 있다가 메인 쓰레드가 시작되면 멈춘 곳에서 다시 작업을 수행한다. 참고로 코루틴이 대기하는 건 쓰레드를 기다리는 코루틴이 쌓인 경우처럼 극히 드문 상황에서 일어난다.
코루틴을 쓰면 뉴스를 별도로 처리하는 작업을 아래처럼 구현할 수 있다.
fun onCreate() {
viewModelScope.launch {
val news = getNewsFromApi()
val sortedNews = news.sortedByDescending { it.publishedAt }
view.showNews(sortedNews)
}
}
시작할 때 봤던 코드와 거의 동일하다. 코드는 메인 쓰레드에서 실행되지만 쓰레드를 블로킹하진 않는다. 코루틴의 중단은 데이터가 오는 걸 기다릴 때 코루틴을 잠시 멈추는 식으로 작동한다. 코루틴이 멈춘 동안 메인 쓰레드는 프로그레스 바를 그리는 등의 다른 작업을 할 수 있다. 데이터가 준비되면 코루틴은 다시 메인 쓰레드를 할당받아서 이전에 멈춘 지점부터 재시작한다.
3개의 엔드포인트를 호출해야 하는 문제는 아래처럼 구현해서 해결할 수 있다.
fun onCreate() {
viewModelScope.launch {
val config = getConfigFromApi()
val news = getNewsFromApi(config)
val user = getUserFromApi()
view.showNews(user, news)
}
}
위 코드는 좋은 방법 같지만 효율적이진 않다. 호출은 순차적으로 일어나서 각 호출이 1초씩 걸릴 경우 전체 함수는 3초가 걸린다.
api를 병렬 호출했다면 3초 대신 2초만에 작업을 끝낼 수 있다. 이 때 async 같이 코틀린이 제공하는 코루틴 라이브러리를 쓸 수 있다. async는 요청 처리를 위해 만들어진 코루틴을 즉시 시작하는 함수로 await 같은 함수를 호출해서 결과를 기다린다.
fun onCreate() {
viewModelScope.launch {
val config = async { getConfigFromApi() }
val news = async { getNewsFromApi(config) }
val user = async { getUserFromApi() }
view.showNews(user.await(), news.await())
}
}
자바스크립트, C# 같은 다른 언어에서 널리 쓰이는 async / await 패턴을 썼다. 효율적으로 작동하고 메모리 누수도 없다.
코루틴은 최신 도구를 통해 동시성 프로그래밍을 최대한 쉽게 구현할 수 있게 돕는다.
'책 > 코틀린 코루틴' 카테고리의 다른 글
[코틀린 코루틴] 8. Job, 자식 코루틴 기다리기 (0) | 2024.02.17 |
---|---|
[코틀린 코루틴] 7. 코루틴 컨텍스트 (0) | 2024.02.17 |
[코틀린 코루틴] 6. 코루틴 빌더 (0) | 2024.02.16 |
[코틀린 코루틴] 3. 중단은 어떻게 작동하는가 (0) | 2024.02.14 |
[코틀린 코루틴] 2. 시퀀스 빌더 (0) | 2024.02.12 |