안녕하세요. 오늘은 Kotlin Coroutine과 비동기에 대하여 작성해보겠습니다.
코루틴에 대해 이해하기 위해서는 우선적으로 비동기 처리에 대한 이해가 선행되어야 합니다.
1. 비동기 처리란
1) 동기와 비동기의 개념
비동기 처리(Asynchronous Task)는 프로그램이 특정 작업을 수행하는 동안 다른 작업도 동시에 진행할 수 있도록 하는 프로그래밍 방식입니다. 이를 통해 프로그램이 특정 작업이 완료될 때까지 대기하지 않고, 다른 작업을 계속해서 수행할 수 있습니다.
이러한 비동기 작업은 특히 I/O 작업(네트워크 요청, 파일 읽기/쓰기 등), 데이터베이스 접근, 사용자 입력 처리 등에서 중요한 역할을 합니다.
동기 처리(Synchronous Task)의 경우 현재 처리가 완료되기 전까지는 다음 처리를 수행할 수 없습니다.
예를 들어, A 처리가 끝나기 전에는 B 처리를 시작할 수 없으며, B 처리가 끝난 후에야 C 처리를 시작할 수 있습니다.
허나, 비동기처리의 경우 처리가 병렬적으로 실행됩니다.
현재 처리가 완료되기를 기다리지 않고, 다른 처리를 동시에 시작할 수 있습니다.
처리의 완료 여부와 상관없이 다음 처리를 계속 수행하며, 나중에 처리가 완료되었을 때 그 결과를 처리합니다.
2) 비동기 처리의 장점
- 작업 효율성 증가: 작업이 순차적으로 진행되지 않고 병렬로 실행되기 때문에, CPU 사용률을 최적화할 수 있습니다.
- 프로그램의 응답성 향상: 사용자 인터페이스(UI)를 차단하지 않고 작업을 수행할 수 있기 때문에, UI가 멈추거나 느려지는 현상을 방지할 수 있습니다.
- 리소스 절약: I/O 작업(네트워크 요청, 파일 읽기/쓰기 등)에서 시간이 많이 소요될 때, CPU를 차단하지 않고 다른 작업을 수행할 수 있어 효율적입니다.
3) 비동기 처리의 단점
- 복잡성 증가: 작업이 병렬로 실행되기 때문에, 작업 간의 동기화(synchronization) 및 데이터 경합 문제(race condition)를 처리해야 합니다.
- 디버깅 및 오류 처리의 어려움: 비동기 작업에서 발생하는 오류는 동기 코드보다 추적하기 어려울 수 있습니다.
- 메모리 관리: 비동기 작업이 지나치게 많아지면 메모리 사용이 증가할 수 있어 관리가 필요합니다.
4) 비동기 처리를 사용하는 경우
1) 네트워크 요청 및 I/O작업
네트워크 요청이나 파일 읽기/쓰기와 같은 작업은 일반적으로 시간이 많이 소요됩니다. 이러한 작업을 동기적으로 수행할 경우,
작업이 완료될 때까지 프로그램이 멈추게 됩니다.
비동기 작업을 사용하면 네트워크 요청을 수행하면서도 사용자 인터페이스(UI)를 지속적으로 업데이트할 수 있습니다.
2) 사용자 입력 처리 및 이벤트 리스너
사용자 입력(예: 버튼 클릭)이나 이벤트 리스너에서도 비동기 작업이 사용됩니다.
예를 들어, 사용자가 버튼을 클릭하면 네트워크 요청을 수행해야 하는 경우,
버튼 클릭 이벤트가 완료될 때까지 UI가 멈추지 않도록 비동기 작업을 사용합니다.
3) 백그라운드 작업 처리
백그라운드에서 수행되어야 하는 작업(예: 데이터베이스 쿼리, 이미지 처리 등)은 메인 스레드에서 실행되지 않아야 합니다.
이 경우에도 비동기 작업을 사용하여 백그라운드 스레드에서 작업을 수행하고, 작업이 완료된 후 UI 스레드에서 결과를 업데이트합니다.
여기까지 Kotlin Coroutine을 이해하기 위해 선행되는 비동기 처리에 대하여 알아보았습니다.
이제부터는 Kotlin Coroutine에 대하여 알아보겠습니다.
2. Kotlin Coroutine
1) Coroutine의 개념
Kotlin의 코루틴(Coroutine)은 비동기 프로그래밍을 위한 강력한 기능을 제공하는 라이브러리로,
코드의 가독성과 유지보수성을 높이면서도 비동기 처리를 효과적으로 수행할 수 있게 합니다.
기존의 스레드 기반 비동기 처리 방식과 달리 코루틴은 스레드처럼 독립적인 실행 흐름을 가지지만,
스레드보다 훨씬 가벼운 구조로 여러 코루틴을 단일 스레드에서 실행할 수 있습니다.
2) Coroutine 사용하기
1) Coroutine의 scope
코루틴을 실행하기 위해서는 특정 스코프가 필요합니다. 스코프는 코루틴이 실행되는 범위를 제한합니다.
- GlobalScope: 애플리케이션이 종료될 때까지 코루틴이 유지되므로 일반적으로 사용하지 않는 것이 좋습니다.
- CoroutineScope: 특정 클래스나 컴포넌트의 생명 주기에 맞추어 코루틴의 생명 주기를 관리할 수 있습니다.
- viewModelScope, lifecycleScope: Android에서 ViewModel과 Activity/Fragment의 생명 주기에 따라 자동으로 코루틴을 관리할 수 있는 스코프입니다.
2) Coroutine의 context와 dispatcher
코루틴의 컨텍스트는 코루틴의 실행 환경을 정의합니다.
CoroutineContext 인터페이스를 사용하여 설정하며, 주로 코루틴이 실행될 디스패처(Dispatcher)를 지정합니다.
디스패처는 코루틴이 어느 스레드에서 실행될지 결정합니다.
- Dispatchers.Main: UI 작업을 수행하기 위해 메인 스레드에서 실행됩니다. Android의 경우 UI를 업데이트할 때 사용합니다.
- Dispatchers.IO: I/O 작업을 수행하기 위해 최적화된 백그라운드 스레드에서 실행됩니다. 파일 읽기/쓰기, 네트워크 호출 등에 사용됩니다.
- Dispatchers.Default: CPU 집약적인 작업에 적합한 디스패처입니다. 일반적인 비동기 작업에서 사용됩니다.
- Dispatchers.Unconfined: 호출한 스레드에서 시작하고, 처음 일시 중단 지점 이후 어느 스레드에서 실행될지 알 수 없는 디스패처입니다. 일반적으로 사용하지 않는 것이 좋습니다.
3) 실제 사용
제가 실제로 Secret Diary라는 개인 프로젝트를 하며 사용했던 코드들로 예시를 보여드리겠습니다.
fun readMyNotice(){
viewModelScope.launch(Dispatchers.IO) {
val userEmail = userRepository.getMostRecentUserName()
val response = SecretDiaryObject.getRetrofitSDService.readUserNotice(userEmail!!)
if(response.isSuccessful){
response.body()?.let {
_notices.value = it
}
} else {
response.errorBody()?.let {
Log.d("read user notice", "Error body: ${it.string()}")
}
}
}
}
이 함수는 retrofit2를 이용해 spring 서버에 접속하여 자신이 작성한 게시물을 DB로 부터 불러오는 코드입니다.
해당 코드는 저의 viewModel안에서 작성하였고 viewModelScope를 사용하여 비동기 처리를 하였습니다.viewModelScope의 경우 viewModel이 생성될 때 코루틴을 시작하고,
소멸할 때 코루틴이 취소되므로 코루틴을 직접적으로 취소할 필요 없이 안전하게 사용할 수 있습니다.
viewModelScope옆의 launch의 경우 코틀린을 생성하고 실행하겠다는 의미입니다.
네트워크 작업을 하므로 Dispatchers.IO를 사용하였습니다.
LaunchedEffect(Unit) {
val userDao = UserDatabase.getDatabase(context).userDao()
val usersRepository: UsersRepository = OfflineUsersRepository(userDao)
withContext(Dispatchers.IO) {
userRoomEmail = usersRepository.getMostRecentUserName()
}
}
우선적으로 해당 코드에 LaunchedEffect라는 함수가 존재합니다.
LaunchedEffect는 Jetpack Compose에서 특정 상태(state)가 변경될 때 비동기 작업을 수행하기 위해 사용하는 Composable 함수입니다.
주로 화면이 초기화될 때 데이터를 로드하거나, 특정 값이 변경될 때 해당 값에 맞춰 비동기 작업을 수행할 때 사용됩니다.
LaunchedEffect는 컴포넌트가 재구성(Recomposition)되더라도 재호출되지 않고, key로 지정된 값이 변경될 때만 다시 실행됩니다.
이를 통해 불필요한 비동기 작업의 반복 실행을 방지할 수 있습니다.
허나, 위 코드와 같이 Unit을 사용하면 최초 화면 구성시에만 딱 한번 호출됩니다.
withContext의 경우 코루틴의 context를 전환하기위해 사용하였습니다.
이 코드의 경우 composable 함수 안에서 사용을 하였는데, room을 이용한 데이터베이스에 접근해야만 하는 작업을 composable안의 UI스레드가하는 것이 아닌 백그라운드 스레드로 전환하고 다시 UI스레드로 돌아오게 하기 위해 사용하였습니다.
여기까지 비동기 처리와 Kotlin Coroutine에 대하여 알아보았습니다.
다음 글은 사실 이전에도 쓴 적있지만 훨씬 업그레이드 되어 돌아온 retrofit2에 관한 주제로 돌아오겠습니다.
'안드로이드 앱개발' 카테고리의 다른 글
안드로이드 에뮬레이터에서 이미지 불러와서 Spring 모바일 앱 서버를 이용해 DB에 저장하기 (6) | 2024.10.08 |
---|---|
Retrofit2를 이용한 안드로이드와 스프링 서버 통신(안드로이드편)(안드로이드 서버통신) (3) | 2024.10.03 |
Android Room 사용하기 (4) | 2024.09.28 |
Jetpack Compose에서 UI 화면 구성과 전환(scaffold, box, navHost) (0) | 2024.09.26 |
Jetpack Compose와 Hilt 이해와 사용 (0) | 2024.09.25 |