[Kotlin Coroutine] 코루틴 빌더와 잡

권대원
8 min readMar 23, 2022

--

스코프빌더

코루틴 빌더의 수신 객체

runBlocking 안에서 this 를 수행하면 코루틴이 수신 객체(Receiver)인 것을 알 수 있습니다.

→ “coroutine#1”:BlockingCoroutine{Active}@36d64342

수신 객체 = extension lambda라고도 부르는데, 람다를 확장한 것 처럼 사용가능.

코드블록이 코루틴을 확장한 것 처럼 사용하는 개념.

취소와 타임아웃(1)

그냥 launch를 하게되면 메인쓰레드에서 돌지만, launch.(Dispatchers.Default)는 Default dispatcher를 이용해서 다른 쓰레드에서 돌게됨.

예제 15

실제로 cancel이 되지 않는다. 우리가 만든 job1은 cancel을 이해하지 못하는 코드이다.

서스펜딩 함수

async는 deferred를 반환한다.

Deferred는 직역하면 연기라는 뜻인데, “결과값 수신을 연기한다” 라는 뜻이다.

Deferred는 Job이다

Deferred는 결과가 있는 비동기 작업을 수행하기 위해 결과 값이 없는 Job을 확장하는 인터페이스이다. 즉, Deferred는 Job이며, 이로 인해 Deferred는 Job의 모든 특성을 갖는다. Job의 상태변수(isActive, isCancelled, isCompleted)와 Exception Hanling 등을 모두 Deferred에서 똑같이 적용할 수 있다.

Deferred에서 결과값 수신 : await()

Deferred에서 결과값을 수신하기 위해 Deferred 인터페이스 상의 await()함수를 이용한다. 코드상에서 await()을 호출하면 main() 함수가 수행되는 코루틴은 IO Thread로 부터 Deferred의 결과가 수신될 때 까지 일시중단 된다.

Deferred는 코루틴 async 블록을 이용해 생성할 수 있다.

suspend fun main() {
val deferred : Deferred<String> =
CoroutineScope(Dispatchers.IO).async {
"Deferred Result"
}
val deferredResult = deferred.await() println(deferredResult)
}

Deferred가 Job과 다른점

Deferred가 Jobd과 다른 점이 있다면, 예외가 자동으로 전파되는 Job과 달리 Deferred는 예외를 자동으로 전파하지 않는다는 점이다. 이는 Deferred가 결과값 수신을 대기해야 하기 때문이다. Deferred는 결과값 수신을 대기하고 예외를 전파하기 위해서는 수신 메서드인 await()을 사용해야 한다.

suspend fun main() {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
when (exception) {
is IllegalArgumentException -> println("More Argument Needed To Process Job")
is InterruptedException -> println("Job Interrupted")
}
}
val deferred = CoroutineScope(Dispatchers.IO).async(exceptionHandler) {
throw IllegalArgumentException()
arrayOf(1, 2, 3)
}
delay(1000)
}

위 코드는 아무것도 출력되지 않는다. 이유는 Deferred가 자동으로 에러를 전파하지 않기 때문이다. Deferred는 미래의 어느 시점에 값을 받는 변수인데, 해당 값이 필요 없는 상황에서 에러를 전파시킬 필요가 없기 때문이다.

그래서 await()을 추가해도 ExceptionHandler에서 에러를 처리하는게 아닌, Main Thread에 에러가 전파되어 MainThread가 에러로 인해 강제종료 된다. 이로 인해 illegalArgumentException이 발생했을 때 “More Argument Needed To Process Job” 메세지가 나오지 않는다.

Deferred의 Exception Handling

그러면 CoroutineExceptionHandling을 동작하게 만드려면 어떻게 바꿔야 할까?

바로 에러를 전파받는 위치에 CoroutineExceptionHanlder를 추가해주는 것이다. 즉, async에는 별도의 CoroutineExceptionHanlder없이 전파 받는 위치에 CoroutineExceptionhanlder를 추가해야 한다.

suspend fun main() {    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
when (exception) {
is IllegalArgumentException -> println("More Argument Needed To Process Job")
is InterruptedException -> println("Job Interrupted")
}
}
val deferred = CoroutineScope(Dispatchers.IO).async {
throw IllegalArgumentException()
arrayOf(1, 2, 3)
}
CoroutineScope(Dispatchers.IO).launch(SupervisorJob() + exceptionHandler) {
deferred.await()
}.join()
}

위의 코드의 맨 마지막 블록을 보면 IO Thread위에 exceptionHandler를 설정한 후 deferred의 결과값 수신을 해당 블록 내부에서 함으로 써 Exception이 처리됨을 알 수 있다. join()을 사용한 이유는 Main Thread가 해당 작업이 끝날 때 까지 종료되지 않도록 하기 위함이다.

CEH와 슈퍼바이저 잡

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

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

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

공유 객체, Mutex, Actor

SupervisorJob, CEH를 통해 예외를 체계적으로 다루는 법을 배웠는데, 여러 쓰레드에서 코루틴을 사용하기에 동시성 문제가 발생함. 공유객체를 어떻게 체계적으로 다룰지 배움

Volatile로 가시성 문제를 해결했다고 해도,가시성이란 counter의 값을 증가시키는 동안에 값을 제대로 볼 수 있는 것인데, 이건 값을 제대로 보는 것 뿐 아니라 가져와서 더하는 행동인데 가져왔을 때에는 다른 쓰레드에서 값을 안올릴 수 있다. 하지만 내가 증가시키는 동안에 다른 쓰레드에서 증가시킬 수 있음. 내가 증가시킨건 예전 값을 증가시킨 것이됨. volatile은 가시성 문제만을 해결할 뿐 동시에 읽고 수정해서 생기는 문제를 해결하지 못합니다.

AtomicInteger가 이 문제에는 적합한데 항상 정답은 아닙니다.

incrementAndGet() 값을 증가시키고 현재 값을 리턴시키는 것인데, 그 과정에 다른 쓰레드가 값을 변경할 수 없게 만든다. 따라서 AtomicInteger가 스레드 안전한 자료구조라 함.

여기서는 항상 옳은 답을 도출하지만 항상 답은 아님.

스레드 한정

newSingleThreadContext를 이용해서 특정한 스레드를 만들고 해당 스레드를 사용할 수 있습니다.

하나의 코루틴 Context를 만들어 내는데, 특정 스레드 하나를 만들어 그 스레드만 쓰게 한다.

항상 같은 스레드에서 실행되는것을 보장하게 됨.

뮤텍스는 상호배제(Mutual exclusion)의 줄임말입니다.

공유 상태를 수정할 때 임계 영역(critical section)를 이용하게 하며, 임계 영역을 동시에 접근하는 것을 허용하지 않습니다.

액터는 1973년에 칼 휴이트가 만든 개념으로 액터가 독점적으로 자료를 가지며 그 자료를 다른 코루틴과 공유하지 않고 액터를 통해서만 접근하게 만듭니다.

--

--