본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[Coroutine] Coroutine scope functions

by 돼지왕 왕돼지 2022. 1. 29.
반응형

 

Approaches before coroutine scope functions

 

#
GlobalScope 은 EmptyCoroutineContext 를 사용하므로 다음과 같은 단점을 갖는다.

  1. 취소하기 어렵다. -> memory leak, cpu res 낭비
  2. Parent 의 scope 속성을 상속하지 않는다. -> unit test 가 어렵다.

 

#
GlobalScope 을 사용하지 않기 위해 CoroutineScope 을 전달하는 경우가 있는데, 이는 또 다른 단점을 야기할 수 있다.
전달받은 scope 를 계속 다른 곳으로 전달하여 사용할 수 있으며, 이 경우 한 곳에서 발생한 exception 이 원하지 않는 다른 job 들을 모두 취소시킬 수 있다. (SupervisorJob 을 사용하지 않았다면)
Exception 이 아니더라도 cancel() 함수를 호출할 수도 있으며, 동일한 문제를 야기한다.

 

 

coroutineScope

 

#
coroutineScope 은 scope 을 시작하는 suspending function 이며, lambda 의 return 값을 return 한다.

 

#
async 나 launch 와 다르게, coroutineScope 의 body 는 바로 호출된다.
그리고 해당 scope 가 끝날 때까지 다른 scope 의 것을 진행시키지 않는다. (concurrency 는 떨어진다.)

 

#
coroutineScope 은 다음의 특징을 갖는다.

  1. parent 의 context 를 상속한다.
  2. 모든 children 이 끝나기를 기다린 후 scope 를 종료시킨다.
  3. parent 가 cancel 되면, 모든 children 을 cancel 시킨다.
  4. 발생한 Exception 등을 rethrow 한다.

 

#

suspend fun longTask() = coroutineScope {
    launch {
        delay(1000)
        val name = coroutineContext[CoroutineName]?.name
        println("[$name] Finished task 1")
    }

    launch {
        delay(2000)
        val name = coroutineContext[CoroutineName]?.name
        println("[$name] Finished task 2")
    }
}

fun main() = runBlocking(CoroutineName("Parent")) {
    println("Before")
    longTask()
    println("After")
}

// Before
// (1 sec)
// [Parent] Finished task 1
// (1 sec)
// [Parent] Finished task 2
// After

 

#

suspend fun longTask() = coroutineScope {
    launch {
        delay(1000)
        val name = coroutineContext[CoroutineName]?.name
        println("[$name] Finished task 1")
	}

    launch {
        delay(2000)
        val name = coroutineContext[CoroutineName]?.name
        println("[$name] Finished task 2")
	}
}

fun main(): Unit = runBlocking {
    val job = launch(CoroutineName("Parent")) {
        longTask()
    }
    delay(1500)
    job.cancel()
}

// [Parent] Finished task 1

 

 

Coroutine scope functions

 

#
coroutineScope 과 비슷하게 scope 을 만드는 function 들이 더 있다.
supervisorScope 은 coroutineScope 와 비슷하지만, Job 대신 SupervisorJob 을 사용한다.
withContext 는 coroutine context 를 수정할 수 있는 coroutineScope 이다.
withTimeout 은 timeout 기능이 있는 coroutineScope 이다.

위 함수들을 "scoping function" 이라고 부르는데, "coroutine scope function" 이 더 정확한 이름이라고 생각. (일반적으로 scope function 이라고 하면, let, run, with, apply, also 등을 말함)

 

#
coroutine builder 와 'coroutine scope function' 을 구분할 필요가 있다.

coroutine builders
1. launch, async, produce 등이 있음.
2. CoroutineScope 의 extension function 으로 정의되어 있음.
3. 호출 param 으로 coroutine context 를 받을 수 있음
4. Job 을 통해 exception 이 전파됨
5. async coroutine 을 시작함

coroutine scope functions (runBlocking 제외)
1. coroutineScope, supervisorScope, withContext, withTimeout 등이 있음.
2. suspending function 으로 정의되어 있음.
3. suspending function continuation 으로부터 coroutine context 를 받음
4. 일반 function 처럼 exception 을 던짐
5. coroutine 을 바로 실행시킴

runBlocking 은 coroutine scope functions 와 비슷하지만 blocking 이고, coroutine scope functions 는 suspending 임을 유의해야 한다.

 

 

withContext

 

 

supervisorScope

 

#
SupervisorJob 을 사용하기 때문에 child 가 exception 을 발생시켜도 cancel 되지 않는다.

 

#
withContext(SupervisorJob()) 은 supervisorScope 를 대체할 수 있는가?
그렇지 않음. withContext 는 여전히 regular Job 을 사용하고, SupervisorJob 은 그 parent 로 작동함.

 

 

withTimeout

 

#
withTimeout 은 coroutineScope 과 동일하지만 timeout 이 있음. 지정한 시간보다 오래 걸리면 job 을 cancel 하고, TimeoutCancellationException 을 발생시킨다.

 

#
TimeoutCancellationException 은 CancellationException 의 subtype 이기 때문에 coroutine builder 안에서 throw 되면, 해당 coroutine 만 cancel 시키고, parent 에 영향을 끼치지는 않는다.

 

#
withTimeoutOrNull 은 exception 을 던지는 대신 timeout 이 되면 null 을 return 한다

 

 

참고 : https://kt.academy/article/cc-scoping-functions

 

반응형

댓글