Skip to the content.

CEH와 슈퍼 바이저 잡

예제 33: GlobalScope

어디에도 속하지 않지만 원래부터 존재하는 전역 GlobalScope가 있습니다. 이 전역 스코프를 이용하면 코루틴을 쉽게 수행할 수 있습니다.

import kotlin.random.Random import kotlin.system.* import kotlinx.coroutines.* suspend fun printRandom() { delay(500L) println(Random.nextInt(0, 500)) } fun main() { val job = GlobalScope.launch(Dispatchers.IO) { launch { printRandom() } } Thread.sleep(1000L) }

Thread.sleep(1000L)를 쓴 까닭은 mainrunBlocking이 아니기 때문입니다. delay 메서드를 수행할 수 없습니다.

GlobalScope는 어떤 계층에도 속하지 않고 영원히 동작하게 된다는 문제점이 있습니다. 프로그래밍에서 전역 객체를 잘 사용하지 않는 것 처럼 GlobalScope도 잘 사용하지 않습니다.

예제 34: CoroutineScope

GlobalScope보다 권장되는 형식은 CoroutineScope를 사용하는 것입니다. CoroutineScope는 인자로 CoroutineContext를 받는데 코루틴 엘리먼트를 하나만 넣어도 좋고 이전에 배웠듯 엘리먼트를 합쳐 코루틴 컨텍스트를 만들어도 됩니다.

import kotlin.random.Random import kotlin.system.* import kotlinx.coroutines.* suspend fun printRandom() { delay(500L) println(Random.nextInt(0, 500)) } fun main() { val scope = CoroutineScope(Dispatchers.Default) val job = scope.launch(Dispatchers.IO) { launch { printRandom() } } Thread.sleep(1000L) }

하나의 코루틴 엘리먼트, 디스패처 Dispatchers.Default만 넣어도 코루틴 컨텍스트가 만들어지기 때문에 이렇게 사용할 수 있습니다.

이제부터 scope로 계층적으로 형성된 코루틴을 관리할 수 있습니다. 우리의 필요에 따라 코루틴 스코프를 관리할 수 있습니다.

예제 35: CEH (코루틴 익셉션 핸들러)

예외를 가장 체계적으로 다루는 방법은 CEH (Coroutine Exception Handler, 코루틴 익셉션 핸들러)를 사용하는 것입니다.

CoroutineExceptionHandler를 이용해서 우리만의 CEH를 만든 다음 상위 코루틴 빌더의 컨텍스트에 등록합니다.

import kotlin.random.Random import kotlin.system.* import kotlinx.coroutines.* suspend fun printRandom1() { delay(1000L) println(Random.nextInt(0, 500)) } suspend fun printRandom2() { delay(500L) throw ArithmeticException() } val ceh = CoroutineExceptionHandler { _, exception -> println("Something happend: $exception") } fun main() = runBlocking<Unit> { val scope = CoroutineScope(Dispatchers.IO) val job = scope.launch (ceh) { launch { printRandom1() } launch { printRandom2() } } job.join() }

CoroutineExceptionHandler에 등록하는 람다에서 첫 인자는 CoroutineContext 두 번째 인자는 Exception입니다. 대부분의 경우에는 Exception만 사용하고 나머지는 _로 남겨둡니다.

예제 36: runBlocking과 CEH

runBlocking에서는 CEH를 사용할 수 없습니다. runBlocking은 자식이 예외로 종료되면 항상 종료되고 CEH를 호출하지 않습니다.

import kotlin.random.Random import kotlin.system.* import kotlinx.coroutines.* suspend fun getRandom1(): Int { delay(1000L) return Random.nextInt(0, 500) } suspend fun getRandom2(): Int { delay(500L) throw ArithmeticException() } val ceh = CoroutineExceptionHandler { _, exception -> println("Something happend: $exception") } fun main() = runBlocking<Unit> { val job = launch (ceh) { val a = async { getRandom1() } val b = async { getRandom2() } println(a.await()) println(b.await()) } job.join() }

예제 37: SupervisorJob

슈퍼 바이저 잡은 예외에 의한 취소를 아래쪽으로 내려가게 한다.

import kotlin.random.Random import kotlin.system.* import kotlinx.coroutines.* suspend fun printRandom1() { delay(1000L) println(Random.nextInt(0, 500)) } suspend fun printRandom2() { delay(500L) throw ArithmeticException() } val ceh = CoroutineExceptionHandler { _, exception -> println("Something happend: $exception") } fun main() = runBlocking<Unit> { val scope = CoroutineScope(Dispatchers.IO + SupervisorJob() + ceh) val job1 = scope.launch { printRandom1() } val job2 = scope.launch { printRandom2() } joinAll(job1, job2) }

printRandom2가 실패했지만 printRandom1은 제대로 수행된다.

joinAll은 북수개의 Job에 대해 join를 수행하여 완전히 종료될 때까지 기다린다.

예제 38: SupervisorScope

코루틴 스코프와 슈퍼바이저 잡을 합친듯 한 SupervisorScope가 있습니다.

import kotlin.random.Random import kotlin.system.* import kotlinx.coroutines.* suspend fun printRandom1() { delay(1000L) println(Random.nextInt(0, 500)) } suspend fun printRandom2() { delay(500L) throw ArithmeticException() } suspend fun supervisoredFunc() = supervisorScope { launch { printRandom1() } launch(ceh) { printRandom2() } } val ceh = CoroutineExceptionHandler { _, exception -> println("Something happend: $exception") } fun main() = runBlocking<Unit> { val scope = CoroutineScope(Dispatchers.IO) val job = scope.launch { supervisoredFunc() } job.join() }

슈퍼바이저 스코프를 사용할 때 주의점은 무조건 자식 수준에서 예외를 핸들링 해야한다는 것입니다. 자식의 실패가 부모에게 전달되지 않기 때문에 자식 수준에서 예외를 처리해야합니다.