코틀린 코루틴을 이용해서 비동기 프로그래밍을 할 때 이해하고, 일반적인 함정을 피하는 방법
Top 10 Coroutine Mistakes We All Have Made as Android Developers
Understanding and Avoiding Common Pitfalls in Asynchronous Programming with Kotlin Coroutines
proandroiddev.com
소개
안드로이드 개발자로써, 코틀린 코루틴은 비동기 프로그래밍 툴킷의 필수 사항인 툴입니다.
이 코루틴은 동시작업들을 쉽게 만들고, 코드를 더 읽기 쉽게 만듭니다. 그리고 이전에 쉽게 적용했던 잘못된 방식으로 발생했던 콜백 지옥을 피할수 있게 도와줍니다.
그러나 코루틴은 나름의 챌린지가 있는 허들이다. 쉽게 기본적인 함정에 빠질 수 있고, 버그를 이끌거나 크래시, 혹은 최적 이하에 퍼포먼스를 보여줄수도 있다.
이 기사에서는, 우리는 많은 사람들이 코루틴을 사용할떄 발생하는 탑10 실수에 대해 설명할 것이다. 그리고 이것들을 피하는 방법에 대해서도 가이드해주겠다. 너가 노련한 개발자이든 코루틴을 처음시작하는 사람이든 이 가이드는 너의 이해를 강화해줄 것이고, 견고한 비동기 코드를 작성하는데에 많은 도움을 줄것이다.
1. 메인 스레드 차단
* 실수
Main dispatcher에서 긴 작업이나, 차단 task를 진행할 경우, UI가 멈추거나, ANR error가 발생될 수 있습니다.
* 발생 원인
이것은 어떤 dispatcher를 사용하고 있는지 잊기 쉽기 떄문에 발생합니다. 특히 복잡한 코드베이스일 경우에요.
개발자는 특정 dispatcher를 지정하지 않고 코루틴을 실행하게 되는데, 이때 기본적으로 Main dispatcher가 사용됩니다.
* 대처 방법
항상 명확한 dispatcher를 지정하고 코루틴을 사용하세요.
// Wrong
GlobalScope.launch {
// Long-running task
}
// Correct
GlobalScope.launch(Dispatchers.IO) {
// Long-running task
}
IO 작업을 위해 Dispatcher.IO를 사용하고, CPU 집중 작업일 경우에는 Dispatchers.Default를 사용하세요. 또 UI 업데이트를 위해서는 Dispatchers.Main을 사용하세요.
2. 코루틴 스코프 계층 무시
* 실수
적절한 코루틴 스코를 구조화하지 않으면, 코루틴이 관리가 되지않습니다
이럴경우 생명주기를 벗어나, 메모리 누수나 크래시를 야기합니다.
* 발생 원인
GlobalScope를 무차별적으로 사용하거나 컴포넌트가 파괴되었을 떄 코루틴을 제대로 취소하지않을 경우
* 대처 방법
코루틴을 특정한 생명주기에 묶어 구조화된 동시성에 사용하세요.
activity나 fragment에서는 lifecycleScope를 사용하거나, viewLIfecycleOwner.lifecycleScope 사용
viewmodel에서는 viewModelScope 사용
// In a ViewModel
viewModelScope.launch {
// Coroutine work
}
이건 코루틴이 정상적으로 취소쇨수 있음을 보장합니다. 연관된 생명주기가 파괴될떄요.
3. 예외 처리를 잘못 전파했을 경우
* 실수
코툴린 내에 적절한 exception 을 핸들링하지 못했을 경우, 예기치 못한 크래시나 실패를 야기합니다.
* 발생 원인?
try-catch 블록이 코루틴 내에서 동일한 방식으로 작동하거나
코루틴 계층 구조에서 예외가 어떻게 전파되는지 이해하지 못한다고 가정합니다.
* 대처 방법
1. 예외를 처리하려면 코루틴 내에서 try-catch를 사용하세요. 코루틴 취소를 알리는 데 사용되며 일반적으로 코루틴이 제대로 취소할 수 있도록 다시 던져야 하기 때문에 CancellationException를 확인하는 데 주의하십시오.
2. 동시성으로 구조화 하고 자식 코루틴의 예외는 부모에게 전파합니다.
viewModelScope.launch {
try {
// Suspended function that might throw an exception
} catch (e: Exception) {
if (e !is CancellationException) {
// Handle exception
} else {
throw e // Rethrow to respect cancellation
}
}
}
대안으로 unhandled exceptions에 대해 CoroutineExceptionHandler를 사용하세요.
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
if (throwable !is CancellationException) {
// Handle unhandled exception
}
}
viewModelScope.launch(exceptionHandler) {
// Suspended function that might throw an exception
}
4. 잘못된 코루틴 빌더 사용
* 실수
잘못된 실행 및 비동기 빌더는 누락된 결과 또는 불필요한 동시성과 같은 의도하지 않은 동작으로 이어집니다.
* 발생 원인?
launch (job 반환) 와 async (결과를 얻기 위한 Deferred 반환) 의 차이점을 제대로 이해하지 못해 발생합니다.
* 대처 방법
1. launch는 너가 결과가 필요없고, 코루틴을 바로 쓰고싶을때 사용하세요
2. async 는 너가 값을 비동기적으로 계산해야할때 사용하세요.
// Using async when you need a result
val deferredResult = async {
computeValue()
}
val result = deferredResult.await()
5. 과한 GlobalScope 사용
* 실수
GlobalScope에 의존하여 코루틴을 실행하면, 코루틴이 필요 이상으로 오래 실행되고, 관리하기 어려워집니다.
* 발생 원인 ?
코루틴의 수명 주기를 고려하는 것을 잊거나 예제와 튜토리얼에서 단순성을 위해 사용할 경우
* 대처 방법
꼭 필요한 경우가 아니라면 GlobalScope를 피하세요. 대신의 적절한 스코프를 사용하도록 구조화하세요.
lifecycleScope는 UI 관련 컴포넌트
viewModelScope는 뷰모델을 위해서
적절한 취소와 함께 커스텀 코루틴 스코프
6. 쓰레드 안전을 고려하지 않는다.
* 실수
적절한 동기화 없이, 여러 코루틴에서 공유된 변하는 데이터에 접근하거나, 수정할때 race condition이 발생됩니다
* 발생 원인 ?
코루틴은 내부적으로 스레드 관리를 하기 때문에 개발자가 신경을 쓰지 않아도 되지만, 공유 자원을 사용할때는 스레드 안정성을 고려해야 하는데 소홀히 할 경우 발생
* 대처 방법
쓰레드 안전을 위한 데이터 구조화 사용
동기성 사용 시 mutex나 atomic 클래스 사용
특정 쓰레드 또는 코루틴으로 변경 가능한 상태를 제한합니다.
val mutex = Mutex()
var sharedResource = 0
coroutineScope.launch {
mutex.withLock {
sharedResource++
}
}
7. 코루틴 취소를 잊는다.
* 실수
더이상 필요하지 않은 코루틴을 취소하지 않으면, 불필요한 자원이 낭비되고 의도하지 않은 이슈가 발생될 수 있습니다.
* 발생 원인
취소 로직을 간과하거나 커스텀 스코프에서 올바르게 핸들링 하지 않았을 경우
* 대처 방법
구조적 동시성을 사용하여 코루틴 취소를 자동화하세요
커스텀 스코프 사용 시 적절한 시점에 취소하도록 보장하세요.
val job = CoroutineScope(Dispatchers.IO).launch {
// Work
}
// Cancel when done
job.cancel()
8. 코루틴 내에 차단
* 실수
Thread.sleep() 과 같은 blocking 콜을 사용하거나, 적절한 디스패처 변경 없이 무거운 계산을 사용할때 기본 스레드가 차단됩니다
* 발생 원인
코루틴을 경량 스레드로 오해하거나, 그안에서 블로킹 작업들이 안전하다고 생각해서
* 대처 방법
코루틴 내에서 블로킹 작업을 피하세요
Thread.sleep() 대신의 suspend function인 delay()를 사용하세요
무거운 계산은 Dispatchers.Default를 사용
// Wrong
launch(Dispatchers.IO) {
Thread.sleep(1000)
}
// Correct
launch(Dispatchers.IO) {
delay(1000)
}
9. withContext 오용
* 실수
부적절한 withContext 사용 하는 경우, 예를들어 불필요하게 중첩하거나 목적을 오해함으로써 코드가 읽기 어렵거나 비효율적이게 되는것
* 발생 원인
withContext의 컨텍스트 스위칭과 스코프에 대한 혼란
* 대처 방법
withContext는 특정 코드 블록의 컨텍스트 전환시에만 사용
필요하지 않다면 withContext 중첩하지말기
withContext 블록은 최대한 작게
// Correct usage
val result = withContext(Dispatchers.IO) {
// Perform I/O operation
}
10. 적절한 코루틴 테스트 하지 않기
* 실수
코루틴 베이스 코드에서 적절한 테스트를 소홀히하거나, 코루틴을 올바르게 처리하지 못하는 테스트를 작성하여 신뢰할 수 없거나 불안정한 테스트를 만드는것
* 발생 원인
비동기 코드 테스트는 더 복잡하고, 개발자들이 코루틴을 위한 테스트 도구에 익숙하지 않을수 있다
* 대처 방법
runBlockingTest를 사용하거나 runTest를 사용하세요. kotlinx-coroutines-test는 코루틴 유닛테스트 용입니다
TestCoroutineDispatcher와 TestCoroutineScope는 코루틴 실행 테스트의 영향력이 있습니다
지연이나 타음아웃을 포함한 테스트 코드를 작성할때에 시간을 적절히 진행시켜야 합니다
@Test
fun testCoroutine() = runTest {
val result = mySuspendingFunction()
assertEquals(expectedResult, result)
}
결론
코루틴은 파워풀합니다. 하지만 큰힘에는 큰 책임이 따릅니다.
이러한 일반적인 실수들을 인지하고 피하는 방법을 이해함으로써 더 효율적이고, 신뢰할수있고 유지보수가 용이한 비동기 코드를 안드로이드에서 작성할 수 있습니다.
기억하세요.
올바른 디스패처 선택
코루틴에 맞는 적절한 라이프사용
주의깊은 예외처리
코루틴 스코프와 취소의 유의
코드를 통한 코루틴 테스트
기타
pitfalls 함정
indispensable 필수 사항
prevalent 널리 퍼진
suboptimal 최적 이하의
unknowingly 자신도 모르게
seasoned 노련한
enhance 강화하다
appropriate 지정하다
intensive 집중적인
properly 적당히
intended 의도된
indiscriminately 무차별적으로
by tying 묶음으로써
ensures 보장하다
associated 연관된
assuming ~이라고 가정하다
confusing 혼란스러운
neglecting 소홀히하는
confine 제한
Overlooking 간과하다
underlying 기본
confusion 구별할 수 없음
nest 중첩
flaky 불안정한
Leverage 영향력
'영어 데일리' 카테고리의 다른 글
Mockk와 Mocking 기본 소개 (24.01.08) (1) | 2025.01.08 |
---|---|
컴포즈 단점에 대한 반박댓글 (25.01.07) (0) | 2025.01.07 |
compose를 사용안하는 10가지 이유 (25.01.06) (0) | 2025.01.06 |
컴포즈에서 다국어하는 법 (25.01.05) (0) | 2025.01.05 |
시니어 안드로이드 개발자 탑10 인터뷰 질문과 답변 (25.01.03) (0) | 2025.01.03 |