중간 점검 차 계속 이론, 개념 공부만 하는 것이 아니라 직접 몇가지 실습을 통해 코루틴을 익혀보려고 한다.
유튜브에서 ‘새차원, 코틀린 코루틴’ 강의를 참고해 공부하면 공식문서 실습코드들을 따라 쳐보면서 공부할 수 있다.
그런데 이게 코드 예제들을 마주할 때마다 새롭고 띠용?,,, 하는게 많다.
내가 이해한건 A라 똑같이 적용해서 풀면 틀리고 B라는 개념이 또 등장한다.
그래서 예제들을 많이 보면서 더 많이 생각하고 더 많이 배우게 되는 것 같다.
fun main() = runBlocking<Unit>{
GlobalScope.launch { // 코루틴2 (코루틴1과 다른 생명주기)
delay(3000L)
println("world!")
}
println("hello,") // 코루틴1
}
/* 실행결과 */
// hello,
fun main() = runBlocking<Unit>{
launch { // 코루틴2 (코루틴1과 같은 생명주기)
delay(3000L) //delay()가 없어도 동일한 결과
println("world!")
}
println("hello,") // 코루틴1
}
/* 실행결과 */
// hello,
// world!
우선 위 예제를 통해 코루틴에서 중요하게 생각하는 Structured concurrency에 대해 이야기 해보려고 한다.
Structured concurrency을 잘 지켜 코루틴을 짜면 더 효율적이고 이쁜 코드 개발이 가능해서 중요하게 생각하는 것 같다.
💡 Structured concurrency (구조화된 동시성)
위에서 첫번째 코드는 runBlcoking 블록 내부에서 또 다른 새로운 코루틴 scope인 GlobalScope를 생성해 사용하고 있다.
즉, runBlocking 도 parent Scope 이고, GlobalScope도 parent Scope 이므로 따로국밥으로 각자의 라이프사이클을 가지고 운영된다는 것이다. 그래서 GlobalScope에서 3000L 대기하는 동안 runBlocking이 끝나면서 실행 자체가 종료되어버리고, “World! ”는 출력되지 못한 것 이다.
반면에 두번째 코드는 runBlcoking 내부에서 launch 되고 있다. ( = this.launch)
이는 runBlcoking과 같은 코루틴 Scope 안에서 코루틴을 생성해 동작하는 것이기 때문에 runBlcoking 블록 안에 모든 내용이 수행될때까지 종료되지 않는다.
이렇게 같은 스코프 내에서 코루틴을 생성하도록 구조화해주면,
코루틴 수행이 정상적으로 보장받을 수 있고, 코루틴 간에 병렬처리에도 좋다는 것이 Structured concurrency 이다.
처음 내가 이 코드를 실습해볼때, 코틀린 파일만 실행하는 법을 몰라서 그냥 앱 단위에서 코드를 실행했다.
한마디로 화면까지 만들어서 액티비티 안에 코드를 적어 실행했다는 것….
그런데 아래와 같은 코드를 작성하게 되면 원래는 “hello,” 만 나와야 한다고 이해했는데 이렇게 모두 출력되는 것이었다….!
???????
같은 Scope 아니니까 main()가 끝나면 println("world!") 도 실행이 안 되어야 하는 것 아닌가…? 이걸로 한참을 고민했다.
// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
main()
}
fun main() = runBlocking<Unit>{
GlobalScope.launch {
delay(3000L)
println("world!")
}
println("hello,")
}
}
/* 실행결과 */
// hello,
// world!
이유는 바로 앱에서 실행했기 때문이었다.
그냥 코틀린 코드에서 main() 함수를 run 하게 되면 main() 가 끝나면 실행 자체가 종료된다. 하지만 앱에서는 main()도 그냥 일종의 하나의 함수일뿐…!! 이 함수가 종료된다고 앱이 종료되는 것은 아니다!!! 그렇기 때문에 GlobalScope 이 여전히 살아있을 수 있는 것이고 “World!” 까지 출력되는 것이다.
// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
var count = 0
binding.btn1.setOnClickListener {
count++
binding.btn1.text = "${count}"
}
binding.blockBtn.setOnClickListener {
main()
Log.d("CorutineTest", "main 끝")
}
}
fun main()= runBlocking{
delay(10000000L)
Log.d("CorutineTest", "hello,")
}
}
/* 실행결과 */
// ANR 에러 발생
Dispatcher를 사용해 코루틴을 생성하는 방법에도 크게 2가지가 있다는 것을 알게 되었다.
1. CoroutineScope(Dispatchers.IO)
// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
var count = 0
binding.btn1.setOnClickListener {
count++
binding.btn1.text = "${count}"
}
binding.blockBtn.setOnClickListener {
main()
Log.d("CorutineTest", "main 끝")
}
}
}
fun main() = runBlocking<Unit>{ // main 스레드
CoroutineScope(Dispatchers.IO).launch { // IO 스레드에서 실행되므로 main과 별개
delay(3000L)
println("world!")
Log.d("CorutineTest", "world!")
}
Log.d("CorutineTest", "hello,")
}
/* 실행결과 */
// hello,
// main 끝
// world!
이렇게 CoroutineScope(Dispatchers.IO) 하게 되면 아예 다른 IO Scope에서 코루틴이 생성되면서 main() 함수는 먼저 종료된다. 메인스레드를 runBlocking 했던 것도 끝나기 때문에 UI와 상호작용할 수 있다. 그리고 아예 다른 Scope에 생성 되었던 delay(3000L) 가 지난 후 world! 가 출력된다.
즉, 서로 다른 범위, 다른 생명주기를 갖기 때문에 구조적 동시성을 제공하지도 않는다.
2. launch(Dispatchers.IO)
// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
var count = 0
binding.btn1.setOnClickListener {
count++
binding.btn1.text = "${count}"
}
binding.blockBtn.setOnClickListener {
main()
Log.d("CorutineTest", "main 끝")
}
}
}
fun main() = runBlocking<Unit>{ // main 스레드
launch(Dispatchers.IO) { // 다른 스레드에서 실행되지만 main 막힘
delay(3000L)
Log.d("CorutineTest", "world!")
}
Log.d("CorutineTest", "hello,")
}
/* 실행결과 */
// hello,
// world!
// main 끝
반면에 runBlocking의 내부에서 아예 새로운 Scope를 만드는 것이 아닌 바로 launch(Dispatchers.IO) 를 하게 될 경우, 메인스레드에서 수행되는 runBlocking 블록의 Scope 생명주기를 따르게 된다.
물론 디스패처를 IO로 설정했기 때문에, IO 스레드에서 코루틴이 생성되어 수행된다. 하지만 생명주기 자체는 즉, runBlocking을 따르기 때문에 IO 스레드에서 생성된 코루틴이 모두 수행되기 전까지 runBlocking 은 끝나지 않는다. (자신의 자식 코루틴을 관리하는 셈) 그래서 delay(3000L)동안 메인스레드를 고스란히 blocking 하면서 UI 입력도 차단되게 된다.
'Android > Coroutine' 카테고리의 다른 글
[안드로이드/Coroutine] Coroutine Dispatcher (0) | 2024.07.18 |
---|---|
[안드로이드/Coroutine] Coroutine(코루틴)이란? (0) | 2024.03.03 |