Approaches before coroutine scope functions
#
GlobalScope 은 EmptyCoroutineContext 를 사용하므로 다음과 같은 단점을 갖는다.
- 취소하기 어렵다. -> memory leak, cpu res 낭비
- 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 은 다음의 특징을 갖는다.
- parent 의 context 를 상속한다.
- 모든 children 이 끝나기를 기다린 후 scope 를 종료시킨다.
- parent 가 cancel 되면, 모든 children 을 cancel 시킨다.
- 발생한 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
'프로그래밍 놀이터 > Kotlin, Coroutine' 카테고리의 다른 글
[coroutine] withContext vs async (0) | 2022.02.01 |
---|---|
[Coroutine] Exception handling in coroutine (0) | 2022.01.30 |
[coroutine] Flow vs RxJava (0) | 2021.05.10 |
[coroutine] SharedFlow & StateFlow (0) | 2021.05.08 |
[kotlin] LazyThreadSafeMode SYNCHRONIZED vs. PUBLICATION (0) | 2021.01.28 |
댓글