반응형
- Coroutine Cancel
- 일반적으로 job으로 연 코루틴은 cancel() 메소드를 통해 취소 가능
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
// reulst
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
- 보통 suspend 함수 내부적으로 isActive를 체크하여 false일때 Exception을 던진다.
if (resumeMode == MODE_CANCELLABLE) {
val job = context[Job]
if (job != null && !job.isActive) {
val cause = job.getCancellationException()
cancelResult(state, cause)
throw recoverStackTrace(cause, this)
}
}
- cancle()의 타겟인 코루틴 내부에 suspend 함수가 있다면, 해당 suspend함수가 CancellationExceoption을 thorw한다. 만약 suspend함수가 없다면 isActive를 체크하여 명시적으로 해당 익셉션을 던지도록 한다.
- Coroutine TimeOut
- 코루틴을 일일히 cancel하지 않고, 특정 시간이후에 취소하도록 할때 withTimeout을 사용하여 취소하도록 할 수 있다.
try {
withTimeout(3000) {
sampleRepository.testClick(value)
.flowOn(Dispatchers.IO)
.collect {
// do Something
}
}
} catch (e: TimeoutCancellationException) {
_isLoading.value = false
}
- withTimout은 TimeoutCancellationException 익셉션을 발생시키면서 종료되기에 try-catch문으로 묶어서 사용해야 앱이 중지 되는걸 방지할 수 있다.
- Coroutine Suspend Function
- 코루틴은 기본적으로 일시 중단이 가능하다.
- 스레드를 블락하지 않고 일시중단된 상태에서 다른 작업을 수행하도록 하는 키워드 suspend를 붙여 일시 중단 함수로 만드는것
- 내부동작은 1부의 cps를 참고
- CoroutineContext
- 코루틴을 어떻게 처리할 것인지에 대한 여러가지 정보(Element)를 포함 (Element의 집합)
- 코루틴 컨텍스트는 interface로 코루틴에 대한 설정 요소(element)를 등록하고 스코프의 속성이 됨
- 코루틴컨텍스트 인터페이스 내부에는 4가지의 메소드가 존재
public operator fun <E : Element> get(key: Key<E>): E?
- get() - 연산자(operator) 함수의 의미로, 매개변수로 주어진 key에 해당하는
Context 요소를 반환하는 함수
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
- fold() - 초기값(initalVlaue)을 시작으로 제공된 병합 함수(operation)를 이용해서
대상 컨텍스트 요소들을 병합한 후 결과를 반환하는 함수
public operator fun plus(context: CoroutineContext): CoroutineContext = ...impl...
- plus() - 현재(기존) Context와 파라미터로 주어진 context가 갖는 요소(Element)들을
모두 포함하는 새로운 Context를 반환
public fun minusKey(key: Key<*>): CoroutineContext
- minusKey() - 현재(기존) Context에서 주어진 key를 갖는 요소(Element)를 제외한
새로운 Context를 반환
- Key / Element?
- Key에 해당하는 요소(Element)란 코루틴컨텍스를 구성하는 요소를 뜻하는데 CoroutineId, CoroutineName, Dispatchers, CoroutineInterceptor, CoroutineExceptionHandler등을 의미
- 즉 key : Elent 타입을 제네릭으로 갖고, key를 기반으로 컨텍스트에 element를 등록하는역할
- elent : 코루틴컨텍스트를 상속, key를 멤버속성으로 포함
- 코루틴컨텍스트 구현체
- 코루틴 컨텍스트는 인터페이스로 이를 구현한 구현체는 기본적으로 3가지의 종류가 존재
1) EmptyCoroutineContext - Singleton(싱글톤), Default Context
- Context를 명시하지 않을 경우 기본적으로 적용되는 GlobalScope에 사용된 Context로, GlobalScope == launc{ }는 같은 싱글톤 EmptyContext를 사용하는 의미이다
2) CombinedContext
- 두개 이상의 컨텍스트가 명시되면 Context간 연결을 위한 컨테이너역할의 Context.
3) Element
- Context의 각 요소(Element)들도 CoroutineConext를 구현한다고 위에서 언급한 의미

- Coroutine Disaptchers
- 코루틴에서는 스레드 풀을 만들고 디스패처를 통해서 코루틴을 배분하도록 한다.
- 디스패처는 자신이 관리하는 스레드풀 내의 스레드의 부하 상황에 맞게 코루틴을 배분한다.
- 안드로이드에서는 이미 디스패처가 생성되어 있어 별도로 생성하거나 정의할 필요가 없다.
- Dispatchers.Main : Android 메인 스레드에서 코루틴을 실행하는 디스패처로 UI와 상호작용하는 작업을 실행하기 위해서 사용한다.
- Dispachers>IO : 디스크 또는 네트워크 I\O 작업을 실행하는데 최적화 되어 있는 디스패처
- Dispachers.Default : CPU를 많이 사용하는 작업을 기본 스레드 외부에서 실행하도록 최적화 되어있는 디스패처로 정렬 작업이나 JSON 파싱 작업등에 최적화 되어 있다.
viewmodelScope.launch(Dispachers.IO).launch {
// working method
}
- CoroutineExceptionHandler
- CoroutineExceptionHandler는 코루틴(job)내부에서 오류가 발생했을 때 에러를 처리할 수 있도록 한 코루틴 컨텍스트이다.
suspend fum main() {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler : $exception")
}
val job = CoroutineScope(Dispachers.IO).launch(exceptionHandler) {
thorw IllegalArgumentException()
}
delay(1000)
}
- Coroutine SupervisorJob
- 코루틴은 코루틴 내부에서 코루틴이 수행 될 수 있으며 깊이는 무한해질 수 있다. 하지만 코루틴 내부에서 수행되는 자식 코루틴에 에러가 생겼을 때 별도의 ExceptionHandler를 설정해주지 않으면 자식 코루틴은 부모 코루틴까지 취소시키게 되며 부모 코루틴이 취소되면 당연히 자식 코루틴도 모두 취소된다.

- 이러한 문제를 방지하기 위해 에러의 전파 방향을 자식으로 한정짓는 것이 바로 SupervisorJob이다.
suspend fum main() {
val supervisor = SupervisorJob()
CoroutineScope(Dispachers.IO).launch(exceptionHandler) {
val firstChildJob = launch(Dispatchers.IO = supervisor) {
thorw AssertionError("첫 째 Job이 AsertionError로 인해 취소")
}
val secondChildJob = launch(Dispachres.Default) {
delay(1000)
println("둘 째 Job이 살아있습니다.")
}
firstChildJob.join()
secondChildJob .join()
}.join()
}

참고
https://medium.com/hongbeomi-dev/코틀린의-코루틴-4-coroutine-context-and-dispatchers-1eab8f175428
https://jaejong.tistory.com/62
https://www.notion.so/Coroutine-2-177a5b30a7234cd79445c542a658e4b8
반응형