코루틴
코루틴(Coroutines) 은 쓰레드(Thread)와 기능적으로는 비슷하지만, 하나의 쓰레드 내에서 여러 개의 코루틴이 실행되는 개념으로 비동기 프로그래밍에 권장되는 동시 실행 설계 패턴입니다.
코루틴은 단일 쓰레드 내에서 여러 개의 코루틴을 실행할 수 있기 때문에, 많은 양의 동시 작업을 처리할 수 있으면서 메모리 절약의 장점이 있습니다.
이유는, 기존 쓰레드는 Context-Switching(CPU가 쓰레드를 점유하면서 실행, 종료를 반복하며 메모리 소모)이 발생하기 때문에 많은 양의 쓰레드를 갖기가 어렵지만
반면에 코루틴은 쓰레드가 아닌 루틴을 일시 중단(suspend) 하는 방식이라 Context-Switching에 비용이 들지 않기 때문입니다.
또한, 지정된 작업 범위 내에서 실행이 되기 때문에 메모리 누수를 방지할 수 있습니다.
즉, 코루틴은 쓰레드의 간소화된 버전이라고 할 수 있습니다.
코루틴 이미지 도식화
주요 개념
코루틴에 대해 이해하기 위해서 기본적으로 알아야 하는 개념은 아래와 같습니다.
- CoroutineScope
- CoroutineContext (Job, Dispatchers)
- CoroutineBuilder (launch, async)
- susfend function
CoroutineScope
코루틴이 실행되는 범위로, 코루틴을 실행하고 싶은 Lifecycle에 따라 원하는 Scope를 생성하여 코루틴이 실행될 작업 범위를 지정할 수 있습니다.
▶ 사용자 지정 CoroutineScope : CoroutineScope(CoroutineContext)
ex) CoroutineScope(Dispatchers.Main) // Dispatchers.Main, Dispatchers.Default, Dispatchers.IO, Job()...
// 메인 쓰레드에서 실행될 사용자 정의 Scope
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
// 메인 쓰레드 작업
}
// 백그라운드에서 실행될 사용자 정의 Scope
CoroutineScope(Dispatchers.IO).launch {
// 백그라운드 작업
}
▶ GlobalScope : 앱이 실행될 때부터 종료될 때까지 실행
// 앱의 라이프사이클동안 실행될 Scope
GlobalScope.launch {
// 백그라운드로 전환하여 작업
launch(Dispatchers.IO) {
}
// 메인쓰레드로 전환하여 작업
launch(Dispatchers.Main) {
}
}
안드로이드 Jetpack 라이브러리(AAC)에서는 코루틴을 쉽게 사용할 수 있도록 각 Lifecycle에 맞는 Scope를 제공해주고 있습니다.
▶ ViewModelScope : ViewModel 대상, ViewModel이 제거되면 코루틴 작업이 자동으로 취소됩니다.
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// ViewModel이 제거되면 코루틴도 자동으로 취소됩니다.
}
}
}
▶ LifecycleScope : Lifecycle 객체 대상(Activity, Fragment, Service...), Lifecycle이 끝날 때 코루틴 작업이 자동으로 취소됩니다.
class MyActivity : AppCompatActivity() {
init {
lifecycleScope.launch {
// Lifecycle이 끝날 때 코루틴 작업이 자동으로 취소됩니다.
}
}
}
▶ liveData : LiveData 대상, LiveData가 활성화되면 실행을 시작하고 비활성화되면 자동으로 취소됩니다.
val user: LiveData<User> = liveData {
val data = repository.loadUser() // suspend function
emit(data)
}
CoroutineContext
코루틴 작업을 어떤 쓰레드에서 실행할 것인지에 대한 동작을 정의하고 제어하는 요소입니다.
주요 요소로는 Job과 Dispatchers가 있습니다.
▶ Job : 코루틴을 고유하게 식별하고, 코루틴을 제어합니다.
val job = CoroutineScope(Dispatchers.IO).launch {
// 비동기 작업
}
job.join() // 작업이 완료되기까지 대기
job.cancel() // 작업 취소
val job1 = Job()
CoroutineScope(job1 + Dispatchers.Main).launch {
// 메인 쓰레드 작업
launch(Dispatchers.IO) {
// 비동기 작업
}
withContext(Dispatchers.Default) {
// 비동기 작업
}
}
val job2 = CoroutineScope(Dispatchers.IO).launch {
// 비동기 작업
}
job1.cancel() // job1이 연결된 코루틴 작업 취소
▶ Dispatchers : 코루틴을 어떤 쓰레드에서 실행할 것인지에 대한 동작을 지정합니다.
Dispatchers.Main : 안드로이드의 메인 쓰레드로, UI 작업을 위해 사용해야 합니다.
예를 들어, UI를 구성하거나 LiveData를 업데이트 할 때 사용됩니다.
Dispatchers.IO : 네트워크, 디스크 I/O 실행에 최적화되어 있습니다.
예를 들어, Retrofit으로 네트워크 통신을 하거나, File이나 Room 데이터베이스에서 데이터를 읽고/쓸 때 사용됩니다.
Dispatchers.Default : CPU 사용량이 많은 무거운 작업 처리에 최적화 되어 있습니다.
예를 들어, 데이터를 가공하거나 복잡한 연산, JSON 파싱을 할 때 주로 사용됩니다.
CoroutineBuilder
위에서 설정한 CoroutineScope와 CoroutineContext를 통해 비로소! 드디어! 코루틴을 실행시켜주는 함수입니다.
주요 요소로는 launch와 async가 있습니다.
▶ launch : Job 객체이며, 결과값을 반환하지 않습니다.
실행 후 결과값이 필요 없는 모든 작업은 launch를 사용하여 실행할 수 있습니다.
CoroutineScope(Dispatchers.Main).launch {
// 결과값이 필요없는 작업
}
▶ async : Deferred 객체이며, 결과값을 반환합니다.
await() 함수를 사용하여, 코루틴 작업의 최종 결과값을 반환합니다.
val deferred = CoroutineScope(Dispatchers.Main).async {
// 결과값
"Hello Coroutine!"
}
val message = deferred.await() // await()함수로 결과값 반환
println(message)
▶ withContext : async와 동일하게 결과값을 반환하며, async와의 차이점은 await()을 호출할 필요가 없다는 것입니다.
async{ }.await() 과 동일하다고 보면 됩니다.
코루틴 내부나 susfend 함수 안에서 구현이 가능하며, 콜백이 필요 없이 코드의 쓰레드 풀을 제어할 수 있기 때문에 네트워크 요청이나 DB 조회 같은 작업에 주로 사용합니다.
init {
viewModelScope.launch { // Dispatchers.Main
val user = getUserInfo() // Dispatchers.Main
}
}
suspend fun getUserInfo(): User = // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO
val response = apiService.getUserInfo() // Dispatchers.IO
if (response.isSuccessful) { // Dispatchers.IO
return@withContext response.body() // Dispatchers.IO
} else { // Dispatchers.IO
return@withContext null // Dispatchers.IO
} // Dispatchers.IO
} // Dispatchers.Main
susfend function
코루틴 안에서만 실행할 수 있는 코루틴 전용 메소드입니다.
susfend 메소드는 일반적인 곳에서 호출할 수 없으며, 반드시 코루틴 안에서만 호출이 가능합니다.
이유는 코루틴의 실행이 일시중단(susfend) 되거나 다시 재개(resume) 될 수 있기 때문에, 컴파일러에게 이 메소드는 코루틴 안에서 실행할 메소드임을 정의하기 위해 메소드명 앞에 "suspend" 를 붙여줘야 합니다.
suspend fun getUser(): User {
...
return user
}
코루틴을 사용하실 땐 세 가지 순서만 기억하세요!
1. 어떤 쓰레드에서 실행할 것 인지 Dispatchers 를 정하고 (Dispatchers.Main, Dispatchers.IO, Dispatchers.Default)
2. 코루틴이 실행될 Scope를 정하고 (CoroutineScope, ViewModelScope, LifecycleScope, liveData...)
3. launch 또는 async로 코루틴을 실행 시키면 됩니다!
Context로 Scope를 만들고, Builder를 이용하여 코루틴을 실행!
'Study > Android' 카테고리의 다른 글
JSON to Kotlin class 플러그인 으로 데이터 클래스 생성하기 (0) | 2022.03.11 |
---|---|
FragmentStateAdapter (0) | 2022.03.07 |
미디어 스토어 사진 가져오는 쿼리 (0) | 2021.11.30 |
앱 서명 jks -> pepk (0) | 2021.11.10 |
CoroutineScope Network Example (0) | 2021.10.21 |