Android 공부/Coroutine

[XX캠퍼스] 06. Kotlin Coroutines & Flow ( CEH와 슈퍼바이저 잡 )

Machine_웅 2022. 7. 25. 17:09
728x90
반응형

GlobalScope

어디에도 속하지 않지만 원래부터 존재하는 전역 GlobalScope가 있습니다.

이 전역 스코프를 이용하면 코루틴을 쉽게 수행할 수 있습니다.

 

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)
}

수행결과 
331

Thread.sleep(1000L)를 쓴 까닭은 main runBlocking이 아니기 때문입니다.

 delay 메서드를 수행할 수 없습니다.

 


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로 계층적으로 형성된 코루틴을 관리할 수 있습니다.

우리의 필요에 따라 코루틴 스코프를 관리할 수 있습니다.

 


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()
}

실행결과 
Something happend: java.lang.ArithmeticException

 

CoroutineExceptionHandler에 등록하는 람다에서

첫 인자는 CoroutineContext 

두 번째 인자는 Exception입니다.

대부분의 경우에는 Exception만 사용하고 나머지는 _로 남겨둡니다.

이후 코틀린컨텍스트에 같이 사용하면 된다. (엘리먼트 확장 가능 ) ( 각각의 try catch를 만드는 것보다 통합적으로 관리가 가능하다 ) 

 


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()
}

실행결과 
Exception in thread "main" java.lang.ArithmeticException
 at FileKt.getRandom2 (File.kt:12) 
 at FileKt$getRandom2$1.invokeSuspend (File.kt:-1) 
 at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)

SupervisorJob ( 예외 통제 )

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

( 일반적으로 예외가 발생하면 cancel을 윗방향 아랫방향으로 보낸다. 자식 , 부모 등 ) 

 

예외발생시 아래방향으로 만 내려보낸다 . 

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)
}

실행 결과 
Something happend: java.lang.ArithmeticException
475

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

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

 


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()
}

suspend fun supervisoredFunc() = supervisorScope {
    launch { printRandom1() }
    launch(ceh) { printRandom2() }
}

 

슈퍼바이저 스코프를 사용할 때 주의점은

무조건 자식 수준에서 예외를 핸들링 해야한다는 것입니다. (  CEH 를 등록해놔야 한다.  ) 

자식의 실패가 부모에게 전달되지 않기 때문에 자식 수준에서 예외를 처리해야합니다.

 

CEH를 등록하지 않으면, 에러를 외부로 전파하게 된다. 

728x90
반응형