kotlinlang.org/docs/coroutines-overview.html
코루틴에 앞서 suspend 함수
- suspend fun 은 중단점(다른 suspend fun 호출)을 가지는 함수, 중단점이 없다면 suspend fun 의 의미가 없을듯
- 보통의 함수는 시작하고 1번만 반환한다.
- suspend fun 은 코루틴 안에서 동작할 때 중단점에서 반환되고 다시 중단점 이후부터 실행되고를 반복한다.
- suspend fun 은 컴파일에 의해 Continuation passing style 로 변환된다. 별다른건 아니고 일반적인 코드형태
- suspend fun 의 마지막에 continuation 이라는 인터페이스를 가진 객체를 전달되고 함수의 종료 지점에 continuation 객체의 resume 이 실행되어 다른 suspend fun이 재시작 될 수 있게 함
- suspend fun 의 각 중단점은 label 이 달리고 switch 문으로 전환되어 각 중단점부터 실행될 수 있게 한다. 즉, 모든 suspend fun 이 중단되고
- continuation 객체는 state machine 이라고 불릴 수 있는데, 중단되는 시점의 상태를 저장하고 모든 suspend fun에 passing 된다.
- 결국 코루틴은 함수들을 서브루틴 대신에 콜백방식으로 전환되는 것이 됨
* 그렇다면 함수실행 영역만 분리된다면 모두 비동기로 실행될수 있을 것으로 생각됨
코루틴
suspend fun 들이 협동하며(co) 실행되는 루틴(routine)
코루틴은 코루틴 빌더에 의해 생성
코루틴 빌더는 여러종류의 코루틴을 반환한다. BlockingCoroutine(runBlocking), StandaloneCoroutine(launch), DeferredCoroutine(async) 등
코루틴 빌더(launch, async...) 는 CoroutineScope 의 확장함수이며, 코루틴 안에서 실행되는 코루틴 블록의 입력으로 CoroutineScope 을 입력해준다.
코루틴 = 코루틴빌더 { 코루틴스코프 ->
실행 블록
...
...
코루틴스코프.coroutineContext[Job] // 현재 코루틴을 가져올 수 있음
}
코루틴은 코루틴 컨텍스트 안에서 실행됨
코루틴 컨텍스트는 코루틴 디스페처를 포함하는데 디스페처는 코루틴 실행시 사용할 스레드 또는 스레드풀을 결정
그래서 모든 코루틴 빌더는 코루틴 디스페처를 파라미터로 입력받을 수 있게 함
코루틴은 디스페처에 따라서 main, defualt, 새로운 스레드에서 동작될 수 있음
withContext 특정 컨텍스트에서 코루틴 블록을 실행하고 실행 결과를 반환함
코루틴 빌더 테스트
fun main() = runBlocking<Unit> {
log("runBlocking scope - ${coroutineContext[Job]}")
launch {
log("launch scope - ${coroutineContext[Job]}")
}
async {
log("async scope - ${coroutineContext[Job]}")
}
withContext(coroutineContext) {
log("withContext scope - ${coroutineContext[Job]}")
}
}
[main] runBlocking scope - BlockingCoroutine{Active}@41906a77
[main] withContext scope - UndispatchedCoroutine{Active}@1cf4f579
[main] launch scope - StandaloneCoroutine{Active}@5b80350b
[main] async scope - DeferredCoroutine{Active}@5d6f64b1
CoroutineScope 테스트
fun main(): Unit {
log("main thread")
/**
* CoroutineScope 는 컨텍스트스코프 객체를 반환
* 코루틴은 아니지만 CoroutineScope 만 구현한 구현체이 코루틴 컨텍스트를 가진다.
* 실제 코루틴은 job, Continuation<T>, CoroutineScope 등의 구현체
* 코루틴스코프의 확장함수 launch, async 등을 이용하여 코루틴을 생성할 수 있다.
* 빌더로 생성된 코루틴은 모두 다른 코루틴컨텍스트를 가진다.며
* 코루틴이 아니므로 withContext 을 호출할 수 없다. (suspend 함수를 호출할 수 없음)
* 현재 실행중인 스레드를 중단하지 않는다.
* 현재 실행중인 스레드의 종료와 상관없이 디스페처에 의해 할당된 스레드에서 동작을 계속한다.
* 메인 스레드가 중단되면 코루틴도 모두 중단됨
* Structured concurrency 에 의해 scope 안의 1개의 코루틴에서 exception이 발생하면 모두 중지됨
* DefaultDispatcher-worker-N 의 이름으로 스레드 생성을 관리, 여러 코루틴이 같은 스레드에서 실행될 수도 있고 다른 스레드에서 실행될 수도 있음
*
*/
val t = thread {
CoroutineScope(Dispatchers.Default).apply {
log("CoroutineScope ${this}")
log("CoroutineScope - ${coroutineContext[Job]}")
val a = launch(Dispatchers.Default) {
log("launch(Default) scope - ${coroutineContext[Job]}")
}
// withContext(Dispatchers.IO) {
// log("CoroutineScope ${this}")
// log("withContext(IO) scope - ${coroutineContext[Job]}")
// }
async {
log("async(Unconfined) scope - ${coroutineContext[Job]}")
delay(1000)
log("async(Unconfined) scope - ${coroutineContext[Job]}")
delay(1000)
log("async(Unconfined) scope - ${coroutineContext[Job]}")
}
}
}
t.join()
println("Thread end")
Thread.sleep(10000)
}
[main] main thread
[Thread-0] CoroutineScope CoroutineScope(coroutineContext=[JobImpl{Active}@1d9788ac, Dispatchers.Default])
[Thread-0] CoroutineScope - JobImpl{Active}@1d9788ac
[DefaultDispatcher-worker-1 @coroutine#1] launch(Default) scope - "coroutine#1":StandaloneCoroutine{Active}@79c294da
[main] Thread end
[DefaultDispatcher-worker-1 @coroutine#2] async(Unconfined) scope - "coroutine#2":DeferredCoroutine{Active}@3b37929e
[DefaultDispatcher-worker-1 @coroutine#2] async(Unconfined) scope - "coroutine#2":DeferredCoroutine{Active}@3b37929e
[DefaultDispatcher-worker-1 @coroutine#2] async(Unconfined) scope - "coroutine#2":DeferredCoroutine{Active}@3b37929e
RunBlocking
현재 스레드를 중단하고 새로운 코루틴을 생성하고 실행한다.
메인 함수 또는 테스트 함수에서만 사용할 것 (일반적인 함수에서 suspend 함수를 호출하기 위한 용도로써)
runBlocking 을 사용하면 현재 스레드가 중단되므로, 다른 곳에서 timeout 이 발생할 수 있다. runBlocking 에서 생성한 코루틴이 끝날때까지 항상 기다리므로
runBlocking 을 사용하기 보단, 메인 스레드로부터 스레드를 할당받아 그곳에서 CoroutinScope 를 생성하고 코루틴을 실행하는 방식으로 해야될 것 같다.
그러면 할당 받은 스레드와 비동기로 각 코루틴 디스페처에서 할당한 스레드에서 코루틴을 동작할 것이며, 할당받은 스레드의 자원도 제대로 반환 될 것임
runBlocking 을 사용하면 현재 스레드를 모두 점유하게 되므로, 함수 내에서 일시중단을 하므로써 스레드를 여러 곳에서 사용하게 하려는 코루틴의 장점이 사라진다.
팁
코루틴 디버깅시 현재 스레드와 현재 코루틴을 로그에 표시하면 편하다. intellij 디버깅 옵션으로 제공됨 (-Dkotlinx.coroutines.debug)
'SW 공부' 카테고리의 다른 글
[Jackson] 상속관계 deserializing (0) | 2021.03.29 |
---|---|
[Ssh] ssh 키를 이용하여 로그인하기 (0) | 2020.11.01 |
[Redis] redis 를 이용한 global session 관리 (0) | 2020.10.15 |
[Spring] service 테스트 작성 요령 (0) | 2020.07.06 |
[JPA] 복합키(Composite Key) 생성 및 관계 맺기 (0) | 2020.04.23 |