본문 바로가기

카테고리 없음

Coroutine 2부

반응형
  • 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://kotlinworld.com/152

https://medium.com/hongbeomi-dev/코틀린의-코루틴-4-coroutine-context-and-dispatchers-1eab8f175428

https://jaejong.tistory.com/62

https://kotlinworld.com/141

https://www.notion.so/Coroutine-2-177a5b30a7234cd79445c542a658e4b8

반응형