Corutine
- 코루틴은 비동기적으로 실행된는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴 입니다. Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확립된 개념을 기반으로 합니다.
다음 두가지 문제를 해결하는데 코루틴은 도움이 됩니다.
- 기본 스레드를 차단하여 앱이 정지될 수 있는 장기 실행작업을 관리합니다.
- 기본 안전,즉 기본 스레드에서 네트워크 또는 디스크 작업을 안전하게 호출하는 기능을 제공합니다.
장기 실행 작업 관리
Android의 모든 앱에는 사용자 인터페이스를 처리하고 사용자 상호작용을 관리하는 기본 스레드가 있습니다. 기본 스레드에 너무 많은 작업을 할당한 앱은은 정지된 것처럼 보이거나 매우 느려질 수 있습니다. 네트워크 요청, JSON 파싱, 데이터베이스 읽기 또는 쓰기, 큰 목록 반복 등으로 앱이 느리게 실행되어 버벅거림(터치 이벤트에 UI가 느리게 반응하거나 멈춰 보이는 현상)이 나타날 수 있습니다. 이러한 장기 실행 작업은 기본 스레드 외부에서 실행해야 합니다.
다음 예는 가상 장기 실행 작업에 대한 간단한 코루틴 구현을 보여줍니다.
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("https://developer.android.com") // Dispatchers.IO for `get`
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
코루틴은 장기 실행 작업을 처리하는 두 작업을 추가하여 일반 함수를 기반으로 빌드됩니다. invoke
(또는 call
) 및 return
외에도 코루틴은 suspend
및 resume
을 추가합니다.
suspend
는 현재 코루틴 실행을 일시중지하고 모든 로컬 변수를 저장합니다.resume
은 정지된 위치부터 정지된 코루틴을 계속 실행합니다.
위의 예에서 get()
은 여전히 기본 스레드에서 실행되지만 네트워크 요청을 시작하기 전에 코루틴을 정지합니다. 네트워크 요청이 완료되면 get
은 콜백을 사용하여 기본 스레드에 알리는 대신 정지된 코루틴을 재개합니다.
기본 안전을 위해 코루틴 사용
Kotlin 코루틴은 디스패처를 사용하여 쿠리틴에 실행에 사용되는 스레드를 결정합니다. 코드를 기본 스레드 외부에서 실행하려면 기본 또는 IO 디스패처에서 작업을 실행하도록 Kotlin 코루틴에 지시하면 됩니다. Kotlin에서 모든 코루틴은 기본 스레드에서 실행 중인 경우에도 디스패처에서 실행되어야 합니다. 코루틴이 정지될 수 있으며 디스패처는 코루틴을 계속해야 합니다.
Kotlin은 코루틴을 실행할 위치를 지정하는데 사용하는 세가지 디스패처를 제공합니다.
- Dispatchers.Main - 이 디스패처를 사용하여 기본 Android 스레드에서 코루틴을 실행합니다. 이 디스패처는 UI와 상호 작용하고 빠른 작업을 수행하기 위해서만 사용해야 합니다. 예를 들어
suspend
함수를 호출하고, Android UI 프레임워크 작업을 실행하고,LiveData
객체를 업데이트합니다. - Dispatchers.IO - 이 디스패처는 기본 스레드 외부에서 디스크 또는 네트워크 I/O를 수행하도록 최적화되어 있습니다. 예를 들어 회의실 구성요소를 사용하고 파일을 읽거나 쓰고 네트워크 작업을 실행합니다.
- Dispatchers.Default - 이 디스패처는 CPU를 많이 사용하는 작업을 기본 스레드 외부에서 수행하도록 최적화되어 있습니다. 예를 들어 목록을 정렬하고 JSON을 파싱합니다.
withContext()의 성능
withContext()
는 상응하는 콜백 기반 구현에 비해 오버헤드를 추가하지 않습니다. 또한 일부 상황에서 상응하는 콜백 기반 구현을 능가하도록 withContext()
호출을 최적화할 수 있습니다. 예를 들어, 함수가 네트워크를 10회 호출하는 경우 외부 withContext()
를 사용하여 스레드를 한 번만 전환하도록 Kotlin에 지시할 수 있습니다. 그러면 네트워크 라이브러리에서 withContext()
를 여러 번 사용하더라도 동일한 디스패처에 유지되고 스레드가 전환되지 않습니다. 또한 Kotlin은 가능한 스레드 전환을 방지하도록 Dispatchers.Default
와 Dispatchers.IO
간의 전환을 최적화합니다.
CoroutineScope 지정
코루틴을 정의할 때 CoroutineScope
도 함께 지정해야 합니다. CoroutineScope
는 하나 이상의 관련 코루틴을 관리합니다. CoroutineScope
를 사용하여 이 범위 내에서 새 코루틴을 시작할 수도 있습니다. 하지만 디스패처와 달리 CoroutineScope
는 코루틴을 실행하지 않습니다.
CoroutineScope
의 한 가지 중요한 기능으로, 사용자가 앱 내의 콘텐츠 영역을 벗어나면 코루틴 실행을 중지합니다. CoroutineScope
를 사용하여 실행 중인 작업이 올바르게 중지되는지 확인할 수 있습니다.
import kotlinx.coroutines.* fun main() { GlobalScope.launch { // launch new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive }
output
“Hello World”
fun test2() {
runBlocking<Unit> {
launch { delay(1000L)
Log.e(TAG, "World")
}
Log.e(TAG, "Hello") delay(2000L)
}
Log.e(TAG, "End function")
}
output
Hello
World
End function
fun main() = runBlocking {
val job = GlobalScope.launch { // launch new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // wait until child coroutine completes
}
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking#2")
}
coroutineScope { // Creates a new coroutine scope
launch {
delay(500L)
println("Task from nested launch#3")
}
delay(100L)
println("Task from coroutine scope#1") // This line will be printed before nested launch
}
println("Coroutine scope is over#4") // This line is not printed until nested launch completes
}
output
Task from coroutine scope#1
Task from runBlocking#2
Task from nested launch#3
Coroutine scope is over#4