[Coroutine] 코루틴 스코프
업데이트:
코루틴 스코프란?
코루틴은 코루틴 스코프 안에서만 동작한다. 특히 일시 중단 함수 즉 await(), join(), delay(), suspend 메소드는 코루틴 스코프 안에서만 호출이 가능하다. 위 api를 코루틴 스코프가 아닌 다른 영역에서 사용하면 컴파일 에러가 발생한다.
위 코루틴 스코프를 시용하기 위해 미리 정의된 코루틴 스코프를 사용하거나 직접 생성하는 방법이 있다.
GlobalScope
GlobalScope는 CoroutineScope의 한 종류로써 가장 큰 특징은 Application이 시작하고 종료될 때까지 계속 유지가 된다. Singletone 이기 때문에 따로 생성하지 않아도 되며 어디에서든 바로 접근이 가능하여 간단하게 사용하기 쉽다는 장점이 있다.
단점으로는 GlobalScope를 사용하면 메모리 누수의 원인이 될 수 있기 때문에 신중히 사용해야 한다. 즉 다시 말해 앱이 실행된 이 후 계속 수행이 되어야 한다면 GlobalScope 를 사용해야 하는 것이고 특정 Activity나 Service 에서만 잠깐 사용하는 것이라면 GlobalScope를 사용하면 안된다.
보통 GlobalScope.launch 를 사용하여 코루틴을 실행한다.
fun main() {
GlobalScope.launch {
//run coroutine
}
}
runBlocking
runBlocking 에 넘겨준 코루틴 로직을 모두 수행 할 때까지 대기를 하게 된다.
아래 코드를 실행하면
fun main() {
runBlocking {
delay(5000)
println("step 1")
}
println("step 2")
}
Step 1 > Step 2 가 실행됨을 항상 보장한다. 왜냐하면 runBlocking의 코루틴이 모두 실행될 때까지 메인 쓰레드가 대기하기 때문이다.
아래와 같이 runBlocking 대신 GlobalScope.launch 로 바꾸면
fun main() {
GlobalScope.launch {
delay(5000)
println("step 1")
}
println("step 2")
}
Step 2 만 출력이 된다. 왜나하면 GlobalScope.launch 는 메인쓰레드를 블록킹하지 않으며 코루틴과 main() 로직을 병렬적으로 실행이 된다. 다만 코루틴이 종료되기 전에 main() 이 먼저 종료되기 때문에 step1이 출력되지 않는다.
runBlocking() 같은 경우 코루틴의 장점인 병렬/비동기 처리에 대한 장점을 활용하지 못하며 특히 Android 메인 스레드에서 호출하면 ANR를 발생할 수 있기 때문에 일반 비즈니스 로직에 사용되지 않으며 주로 Unit test 에 사용한다.
withContext
현재 실행되고 있는 코루틴의 컨텍스트를 변경할 때 사용된다. 가장 대표적인 예는 Android 기준으로 http request를 수행하고 그 결과를 화면에 표시한다고 가정하면 아래와 같은 코드가 될 것이다.
GlobalScope.launch(IO) {
val result = fetch(httpUrl)
withContext(Main) {
resultTextView.text = result.toString()
}
}
참고로 안드로이드 메인 쓰레드에서는 HTTP request 동작을 수행할 수 없으며 UI 변경(TextView 변경) 은 반드시 메인 쓰레드에서만 수행해야 하기 때문에 컨텍스트 스위칭(쓰레드 스위칭)은 반드시 수행이 되어야 한다.
위 코루틴을 사용하지 않는다면 가장 고전적인 방법으로 핸들러를 사용하여 메인 쓰레드에 메시지를 전송해야 하는 번거로움을 감수해야 한다.
CoroutineScope
위에서 미리 정의된 코루틴 스코프를 사용하는 것이 심플하고 편하지만 제일 이상적인 것은 필요할 때 생성하고 필요하지 않을 때 정리를 하는 것이다.
코루틴 스코프는 아래와 같이 생성하고 정리를 하면 된다.
val scope = CoroutineScope(Dispatchers.Main)
val job = scope.launch {
// run..
}
job.cancel()
보통 Android Activity에서는 다음과 같이 정의하여 코루틴 스코프와 Activity 의 생명 주기와 일치한다.
class SearchActivity() : CoroutineScope { // 코루틴 스코프를 상속
private lateinit var job: Job
override val coroutineContext: CoroutineContext // 코루틴 컨텍스트를 재정의
get() = Main + job
override fun onCreate(savedInstanceState: Bundle?) {
...
job = Job()
...
}
fun doOnBackground() {
launch(IO) {
// network, file I/O, DB operation
withContext(Main) {
// Update UI
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
위 코드는 Activity가 destory가 되면 백그라운드로 실행 중인 코루틴이 job.cancel() 를 통해서 정리가 됨으로써 필요할 때 생성하고 필요하지 않을 때 정리를 하는 원칙 을 준수하게 된다.
Reference
코틀린 동시성 프로그래밍(에이콘)
댓글남기기