코루틴
코루틴(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를 이용하여 코루틴을 실행!
'코틀린' 카테고리의 다른 글
코틀린(Kotlin) - 타입 시스템 null 가능성 safe call(?.) elvis(?:) (0) | 2021.03.21 |
---|---|
코틀린(Kotlin) - lambda with receiver(수신 객체 지정 람다) : with, apply (0) | 2021.03.16 |
코틀린(Kotlin) - 함수형 인터페이스 활용 (0) | 2021.02.24 |
코틀린(Kotlin) - 지연 계산(lazy) 컬렉션 연산 : Sequence (0) | 2020.04.24 |
코틀린(Kotlin) - 컬렉션 API : filter, map, all, any, count, find, groupBy, flatMap, flatten (1) | 2020.04.03 |