반응형
안녕하세요
오늘은 Kotlin의 Coroutine에 대해서 간략하게 포스팅 해보겠습니다.
Coroutine 이란?
비동기로 실행되는 코드를 단순화하기 위해 Android에서 사용할 수 있는 동시성 디자인 패턴
- 메인 스레드를 차단하고 앱이 응답하지 않게 만들 수 있는 작업을 관리하는데 도움이 됩니다
특징
- Lightweight : 실행 중인 스레드를 차단하지 않는 Suspend 로 인해 단일 스레드에서 많은 Coroutine 실행 가능
- 메모리 누수 감소 : 구조화된 동시성 (Structured concurrency)을 사용하여 범위 내에서 작업을 실행
* 구조화된 동시성- 새로운 Coroutine은 Coroutine의 수명을 구분하는 특정 CoroutineScope에서만 시작이 가능합니다.
- 손실되지 않고 누출되지 않도록 합니다.
- 코드의 모든 오류가 올바르게 보고되고 손실되지 않도록 합니다.
예제 살펴보기
종속성
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}
변경 전 : 백그라운드 스레드에서 실행
메인 스레드에서 네트워크 요청은 차단으로 인해 ANR을 발생할 수 있습니다. 그러기 때문에, 백그라운드에서 스레드를 실행합니다.
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// 현재 스레드를 차단하는 네트워크 요청을 생성하는 함수
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
- LoginRepository의 makeLoginRequest가 네트워크 동기식 호출로 호출 스레드를 차단합니다.
- 네트워크 응답 요청을 위해 Result 클래스가 사용됩니다.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
- LoginViewModel 내에서 사용자 버튼 클릭 후 네트워크 요청을 수행합니다.
- 네트워크 요청 시 UI 스레드를 차단합니다.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// UI thread의 실행을 옮기기 위한 새로운 Coroutine 생성
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
- 메인 스레드에서 실행을 이동하는 코드는 새 Coroutine을 생성하여 I/O 스레드에서 네트워크 요청을 실행하는 것입니다.
- viewModelScope.launch : Coroutine을 생성하고, 함수 본문의 실행을 디스패처로 전달합니다
- Dispatchers.IO : I/O 작업을 위해 예약된 스레드에서 실행되어야 한다는 의미입니다.
- login 함수 상세
- View 계층으로부터 login 함수는 메인 스레드에서 실행됩니다.
- launch는 새로운 coroutine을 생성하고, 네트워크 요청이 I/O 명령을 위한 예약된 스레드위에서 독립적으로 이루어집니다.
- Coroutine이 실행되는 동안 login 함수는 실행을 계속하고, 네트워크 요청이 완료되기 전에 리턴합니다.
(지금은 네트워크 응답이 무시되는 코드)
- viewModelScope에서 시작되므로, ViewModel 안에서 실행됩니다.
- ViewModel이 Destroy 된다면, viewModelScope는 자동으로 취소되고, 모든 coroutine도 취소됩니다.
- 현재 코드에서는 makeLoginRequest 함수를 메인스레드 실행을 옮겨야 합니다.
변경 후 : Main-Safety를 위한 Coroutine 사용
main-safe : 메인 스레드에서 UI 업데이트를 차단하지 않게 하는 것
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// 코루틴 실행을 Dispatchers로 이동
return withContext(Dispatchers.IO) {
// 네트워크 요청 코드 차단
}
}
}
- 다른 스레드로 Coroutine의 실행을 이동하려면 Coroutine 라이브러리의 withContext() 을 사용합니다.
- withContext(Dispatchers.IO) 는 I/O 스레드로 Coroutine 실행을 이동합니다. 필요에 따라 UI 업데이트도 가능합니다.
- makeLoginRequest의 suspend 사용은 Coroutine 내에서 호출되는 함수를 강제하기 위한 Kotlin의 특징입니다.
makeLoginRequest를 메인스레드 실행에서 옮기면서, login 함수의 Coroutine을 메인스레드에서 실행할 수 있습니다.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// UI thread에서 새로운 coroutine 생성
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// 작업이 끝날때 까지, suspend 실행과 네트워크 호출을 생성합니다.
val result = loginRepository.makeLoginRequest(jsonBody)
// 사용자에 네트워크 요청의 결과를 출력합니다.
when (result) {
is Result.Success<LoginResponse> ->
else ->
}
}
}
}
- makeLoginRequest는 suspend 함수이고, Coroutine에서 실행되어야 하므로 Coroutine은 여전히 필요합니다.
- 이전 코드와 비교
- launch는 Dispatchers.IO 파라미터를 사용하지 않습니다.
(launch가 Dispatcher를 전달하지 않을 때, viewModelScope에서 모든 Coroutine은 메인 스레드에서 실행됩니다. - 네트워크 요청 성공, 실패 결과를 UI에서 처리하게 됩니다.
- launch는 Dispatchers.IO 파라미터를 사용하지 않습니다.
- login 함수는
- 메인스레드 View 계층에서 실행됩니다.
- launch는 새로운 Coroutine을 생성하고, 실행을 시작합니다.
- Coroutine 내에서 loginRepository.makeLoginRequest()는 makeLoginRequest()의 withContext 블록 실행이 완료 될때 까지 Coroutine의 추가 실행을 일시 중단 합니다.
- withContext 블록이 완료되면, login에서 Coroutine가 실행을 재개합니다.
이번에는 Kotlin의 Coroutine 및 사용법에 대해 알아보았습니다
잘못된 내용이 있다면 댓글 부탁드리고, 내용이 좋았다면 공감, 구독 부탁드려요!
반응형
'DEV > Android' 카테고리의 다른 글
[TIL-230626] Android Flow 알아보기 - 2 (0) | 2023.06.27 |
---|---|
[TIL-230626] Android Flow 알아보기 - 1 (0) | 2023.06.26 |
[TIL-230622] Retrofit 알아보기 (0) | 2023.06.22 |
[TIL-230612] 데이터 출력을 위한 List 구현 (0) | 2023.06.12 |
[TIL-230606] 화면 이동을 위한 NavigationController 구현 (0) | 2023.06.07 |