디벨러퍼 공식문서를 바탕으로 코루틴으로 백그라운드 작업을 실행시키는 방법을 알아보겠습니다.
#Coroutine - 백그라운드 작업
Developer Instruction to coroutines - https://developer.android.com/kotlin/coroutines?hl=en
서버에 로그인을 요청하는 Developer 사이트의 예제입니다
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
- ViewModel을 사용하고, viewModelScope을 Scope으로 지정해 코루틴을 생성/사용하고 있습니다
- viewModelScope은 해당 뷰모델 destroy시 실행중인 모든 코루틴이 취소 되므로 뷰모델이 활성화 상태인 경우에만 실행해야 할 작업이 있을 때 유용 합니다
- login 함수는 Main Thread에서 실행 될 것이고 makeLoginRequest 함수는 네트워크 작업이므로 백그라운드에서 실행시켜야 합니다.
- launch 함수 실행시 Dispatcher.IO를 지정해 I/O 작업 전용으로 예약된 스레드에서 실행되게끔 합니다
- Dispatcher 지정으로 코루틴 실행에 사용되는 스레드를 정할 수 있습니다.
*Dispatcher?
Dispatcher?
- 코루틴은 Dispatcher를 보고 코루틴이 실행될 Thread를 결정
- 코루틴은 자체적으로 suspend 될 수 있고, Dispatcher 는 다시 resume을 담당
- Dispatcher 종류는 3가지
- Dispatcher.Main : Main Thread에서 코루틴이 실행 됨
- UI 작업이나 짧은 작업을 실행하기 위해서만 사용
- Dispatchers.IO : Worker Thread에서 코루틴이 실행 되며, 이 Thread는 Disk작업이나 Network I/O 작업에 최적화 되어 있다
- DB 접근, 파일 쓰기, 네트워크 작업 등에 사용
- Dispatchers.Default : Worker Thread에서 코루틴이 실행 되며, 이 Thread는 CPU 사용량이 많은 작업에 최적화되어 있다
- 리스트 정렬, JSON 파싱 등에 사용
- Dispatcher.Main : Main Thread에서 코루틴이 실행 됨
이 예제에서 makeLogoinRequest()는 Worker Thread에서 실행됨을 가정하고 있습니다.
즉 makeLogoinRequest를 호출 하는 쪽에서는 이 함수를 사용할 때 Worker Thread에서 동작하게끔 신경을 써주어야 합니다(Dispatcher.IO를 지정하는 등의 방법으로)
혹시나 사용하는 쪽에서 실수로 Main Thread에서 makeLogoinRequest를 호출했다면 이는 Main Thread를 Blocking 하는 사태를 발생시킵니다. 고로 이런 함수는 main-safe 하지 않습니다.
그렇다면 makeLogoinRequest() 함수를 main-safe한 함수로 변경해 봅시다.
#Coroutine - 백그라운드 작업 with main-safe function
main-safe한 makeLogoinRequest() 함수 입니다
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// 네트워크 코드 작성 (I/O Thread 내부에서 동작)
}
}
}
- withContext는 코루틴 내에서 사용되며 코루틴 코드 일부를 지정한 Dispatcher에 해당하는 Thread에서 실행되게끔 합니다. 즉, 다른 컨텍스트로 코루틴을 전환 합니다.
- 네트워크 관련 코드를 withContext의 람다로 넘기고 Dispatchers.IO 지정을 통해 I/O Thread에서 동작하게 됨으로, 이 예제에서의 makeLogoinRequest 함수는 Main Thread에서 호출 하더라도(가정), 네트워크 작업이 Worker Thread에서 동작이 보장 되는 main-safe 함수입니다 -> 호출하는 입장에서 함수가 실행될 Thread를 생각할 필요가 X
그럼 main-safe 한 makeLogoinRequest() 함수를 호출하는 LoginViewModel의 login() 함수를 다시 보겠습니다
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
- makeLoginRequest가 suspend 함수이므로 호출하기 위해서 여전히 코루틴을 하나 만들어야 합니다(suspend 키워드를 붙이면 해당 함수가 코루틴 내부에서 호출되도록 강제됩니다)
- viewModelScop에서 실행된 코루틴은 Dispatchers를 미지정시 기본으로 'Dispatchers.Main.immediate'이 셋팅됩니다.
- lanuch로 생성된 코루틴은 Main Thread에서 실행되게 되고, makeLogoinRequest() 함수는 Main Thread에서 호출 됩니다(main-safe 하게!)
- makeLogoinRequest()는 suspend 함수 이므로 함수 동작이 완료 될 때 까지 코루틴을 일시 정지 합니다
- makeLogoinRequest() 네트워크 작업이 완료되면(withContext 블록이 완료되면) 작업 완료 결과와 함께 Main Thread에서 실행이 다시 Resume 되고, 성공 or 실패 UI작업도 할 수 있게 됐습니다.
- 이처럼 순차적으로 코드를 나열 되어 있어서 네트워크 작업이 Main Thread를 Block 하는 것 처럼 보일 수 있지만 Main Thread를 Block 하지 않습니다
*Developer Instruction to coroutines - https://developer.android.com/kotlin/coroutines?hl=en
*Main-safety
잘못된 내용이나 궁금한 내용 있으시면 댓글 달아주세요
좋은 하루 되세요!
'Kotlin' 카테고리의 다른 글
[Kotlin] Java 컬렉션 Kotlin에서 사용하기, Kotlin 배열 (0) | 2021.12.28 |
---|---|
[Kotlin] 5. Not-null assertion, let 함수, lateinit (0) | 2021.12.15 |
[Kotlin] 5. Nullable 타입, Safe Call, 엘비스 연산자, Safe Cast (0) | 2021.12.12 |
[Kotlin] CoRoutines (0) | 2021.12.07 |
[Kotlin] 4. 람다 - with/apply (1) | 2021.12.05 |