반응형
Structured Concurrency(구조화된 동시성)
누가 기존의 동작을 취소할 수 있는가?
해당 동작은 Application의 LifeCycle을 충분히 준수하는가?
예외가 발생하면 이를 누가 처리하는가?
→ 이에 대한 물음을 해소하기 위해Coroutine
은Structed Concurrency
를 내세운다.- 이 개념을 베이스로 만들어진,
Scope
,launch
나async
블록을 사용하면,
개발자는 메모리 누수 및 동시성을 가진Function
들을 올바르게 처리할 수 있게 된다.
예시
GlobalScope.launch
는top-level Coroutine
을 만든다.- 비록 Light-Weight이기는 하나, 여전히 Memory를 비롯한 Resource를 사용한다.
→ 새롭게luanch
된coroutine
이 돌고있다는 사실을 잊는다면,GloabalScope
는process
가 살아 있는 한, 계속 유지되기 때문에 로직이 계속 돌것이다. GlobalScope
에 계속해서launch
하는 작업은, 반복해서 의미없는 코드가 돌거나, 너무 많은Coroutine
이 돌아 메모리면에서 OOM 현상이 발생할 수 있고, 관리면에서 쉽게 에러를 유발할 수 있다.
- 비록 Light-Weight이기는 하나, 여전히 Memory를 비롯한 Resource를 사용한다.
- 만약
GloablScope.launch
만 사용한다면, 이를 관리하기 위해reference(job)
을 모두 가지고 있으면서join
을 수동으로 관리해야 한다 → 이는 에러를 발생시키기 쉬운 형태이다. - 이러한 수동작업을 해결하기 위해, Structured Concurrency(구조화된 동기화)를 사용할수 있다.
- 이는
GlobalScope
에서launch
하는 대신,Thread
를 사용하듯이 우리가 사용하는Context
범위 내에서 특정한Coroutine Scope
를 만들고, 그 안에서 새로운CoroutineBuilder
로Coroutine
을launch
하는 것이다. - 그렇게 시작하면 생성된
CoroutineScope
가 코드블럭의CoroutineScope
에 더해진다.
= 모든CoroutineBuilder
는 코드 블럭에 “CoroutineScope
” Instance를 추가한다.Coroutine
이 다른Coroutine
의coroutinScope
내에서 실행되면,CoroutineScopre.corotuineContext
를 통해,Context
를 상속받고,부모 Coroutine job
의 자식이 된다.GlobalScope
는부모 Coroutine
의 영향을 받지 않는다.
- 따라서 외부
Scope
는 내부의Coroutine
들이 종료되기 전까지는 종료되지 않으므로, 우리는 이scope
에서join
을 명시적으로 부를 필요없이coroutine
을launch
하지 않는 간단한 코드를 만들수 있다.
→부모 coroutine
은 항상children
의 완료를 기다리며,parent
는 명시적으로 children의launch
를 track할 필요가 없고Job.join
으로 그들을 기다릴 필요도 없다.
runBlocking{
val request = launch{
repeat(3) { i ->
launch{
delay((i + 1) * 200L)
println(“Coroutine $i is done”)
}
}
println(“request: I’m done and I don’t explicitly join my children that are still active”)
}
request.join()
println(“Now processing of the request is complete”)
}
/* OUTPUT */
request: I’m done and I don’t explicitly join my children that are still active
Coroutine 0 id done
Coroutine 1 id done
Coroutine 2 id done
Now processing of the request is complete
Coroutine
외부 블럭은 내부에서 실행되는Coroutine
이 모두 완료되야만 외부 블럭이 완료된다.
fun test2_1() {
runBlocking {
val jobs = List(10) {
launch {
delay(1000L)
Log.e(TAG, "aaa")
}
}
// join을 하고 안하고에 따라 End runBlock이 먼저 찍힐지 끝나고 찍힐지가 결정된다
// jobs.forEach { it.join() }
Log.e(TAG, "End runBlock ")
}
Log.e(TAG, "End function")
}
- 위 코드에서는
runBlocking coroutinebuilder
로 함수에coroutine
영역으로 만들었다. - 그리고 그 안에서 새로운
coroutine
을launch
한다 (list를 생성하면서 10개를 launch 시킴) runBlocking
은내부 coroutine
인 list의 열번이 다 수행될때까지 block 되며,launch
로 실행한 비동기 동작(자식)이 모두 끝나야runBlocking
{} 블럭이(부모) 종료된다.End runBlock aaa aaa ... End Function
End runBlock
aaa
aaa
...
End Function
- 로그는 위와 같이 나오며
- 주석처리된
join()
을 넣으면, 아래와 같은 코드 실행순서가 보장되면서 찍힌다.
aaa
aaa
...
End runBlock
End Function
join
은 실행 순서를 보장하기위해 쓰였다.- 코드 순서를 보장하기 위해
내부 coroutine
을join
시킬수도 있지만,Coroutine Scope
를 내부에 만들어 순서를 보장할 수도 있다.
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking#2")
}
coroutineScope { // Creates a new coroutine scope
launch {
delay(500L)
println("Task from nested launch#3")
}
delay(100L)
println("Task from coroutine scope#1") // This line will be printed before nested launch
}
println("Coroutine scope is over#4") // This line is not printed until nested launch completes
}
Task from coroutine scope#1
Task from runBlocking#2
Task from nested launch#3
Coroutine scope is over#4
coroutineScope
을 이용하여내부에 또다른 scope
을 만든다.- 위에서 모든
coroutine
블럭은 내부(자식) 코루틴이 모두 완료될때까지 대기해야 한다. - 이런 이유로 "Coroutine scope is over"은
coroutineScope{...}
이 끝날때 까지 기다렸다가 찍힌다
반응형
'IT > Kotlin' 카테고리의 다른 글
[Kotlin/Coroutine] 7. Coroutine 작성시의 테스트와 디버깅 (0) | 2023.06.19 |
---|---|
[Kotlin/Coroutine] 5. Coroutine의 예외처리와 취소 (0) | 2023.05.08 |
[Kotlin/Coroutine] 4. Coroutine의 구성요소: Coroutine의 생성과 활용 (0) | 2023.04.22 |
[Kotlin/Coroutine] 3. launch와 async의 Job과 Deferred (0) | 2023.04.07 |
[Kotlin/Coroutine] 2. Kotlin에서의 Coroutine: launch와 async (0) | 2023.03.24 |