일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- mst
- 수학
- 이분탐색
- 프로그래머스
- swea
- 백트래킹
- 시뮬레이션
- 세그먼트 트리
- 투 포인터
- 백준
- 플로이드-와샬
- 위상정렬
- dfs
- 스택
- Kotlin
- JUnit 5
- java
- BFS
- CS
- 그리디
- 알고리즘
- 완전탐색
- 유니온 파인드
- 에라토스테네스의 체
- Effective Java
- 구현
- Network
- 동적계획법
- 문자열
- 후니의 쉽게 쓴 시스코 네트워킹
반갑습니다!
[Kotlin] Coroutine Context and Dispatchers 본문
코루틴에는 Coroutine Context와 Dispatcher라는 것이 있다. 이번엔 그것들에 대해서 알아보자.
Dispatchers and Threads
코루틴은 기본적으로 Coroutine Context 실행되는데, Context 요소 중에는 Dispatcher가 있다. 그리고 이 Dispatcher라는 것은 코루틴이 어떤 쓰레드에서 실행될지를 결정해준다. runBlocking
, launch
, async
등의 코루틴 빌더 함수에는 Courinte Context를 매개변수로 전달해줄 수 있다.
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
launch { // main runBlocking coroutine
println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // not confined
println("Unconfined: I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("Default: I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) {
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
}
/* 실행결과
Unconfined: I'm working in thread main
Default: I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread
main runBlocking: I'm working in thread main
*/
이처럼 어떤 값을 매개변수로 전달하는지에 따라서 실행되는 매 변수가 달라진다. 매개변수로 아무런 값도 전달하지 않는다면 자신을 호출한 코루틴 스코프의 context를 상속받아 작업을 하게 된다. 즉, 위의 예제에서는 `runBlocking 과 같은 coroutine context에서 실행되므로 메인쓰레드에서 실행된다.
주의할 점으로는 newSingleThreadContext는 새로운 쓰레드를 생성해서 코루틴을 동작시키기 때문에 메모리 릭이 발생할 가능성이 있다.
newSingleThreadContext("MyOwnThread").use {
launch(it) {
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
}
다음과 같이 자동으로 자원을 반납해주는use()
를 사용해서 작성하는 것이 좋다.
Debugging coroutines and threads
코루틴은 쓰레드를 넘나들거나 비동기 작업을 하는 등 디버깅하기가 어렵다. 어떤 코루틴에서 실행된 것인지 알기 위해서는 JVM에 -Dkotlinx.coroutines.debug
옵션을 주면 된다.
import kotlinx.coroutines.*
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main() = runBlocking<Unit> {
val a = async {
log("I'm computing a piece of the answer")
6
}
val b = async {
log("I'm computing another piece of the answer")
7
}
log("The answer is ${a.await() * b.await()}")
}
/* 실행결과
[main @coroutine#2] I'm computing a piece of the answer
[main @coroutine#3] I'm computing another piece of the answer
[main @coroutine#1] The answer is 42
*/
Jumping between threads
코루틴은 withContext()
를 사용해서 context의 전환을 쉽게 할 수 있다.
import kotlinx.coroutines.*
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main() {
newSingleThreadContext("Ctx1").use { ctx1 ->
newSingleThreadContext("Ctx2").use { ctx2 ->
runBlocking(ctx1) {
log("Started in ctx1")
withContext(ctx2) {
log("Working in ctx2")
}
log("Back to ctx1")
}
}
}
}
/* 실행결과
[Ctx1 @coroutine#1] Started in ctx1
[Ctx2 @coroutine#1] Working in ctx2
[Ctx1 @coroutine#1] Back to ctx1
*/
위 예제에서는 context의 전환뿐만 아니라 newSingleThreadContext()
와 use()
를 같이 사용했다는 것을 볼 수 있다.
Job in the context
코루틴에서 Job 객체는 context의 일부이다. 이번엔 Job 객체를 확인해보자.
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
println("My job is ${coroutineContext[Job]}")
launch {
println("My job is ${coroutineContext[Job]}")
}
async {
println("My job is ${coroutineContext[Job]}")
}
}
/* 실행결과
My job is BlockingCoroutine{Active}@579bb367 [main]
My job is StandaloneCoroutine{Active}@7ab2bfe1 [main]
My job is DeferredCoroutine{Active}@497470ed [main]
*/
이처럼 coroutineContext에는 직접 접근할 수 있다.
Children of a coroutine
코루틴 스코프 안에서 코루틴이 실행되면 그 코루틴은 부모 코루틴의 자식이 된다. 이 때 GlobalScope
는 어플리케이션 전체에서 실행되는 코루틴이기 때문에 예외가 된다.
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
val request = launch {
GlobalScope.launch {
println("job1: I run in GlobalScope and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
}
launch {
delay(100)
println("job2: I am a child of the request coroutine")
delay(1000)
println("job2: I will not execute this line if my parent request is cancelled")
}
}
delay(500)
request.cancel()
delay(1000) // 어떤 일이 일어나는지 확인하기 위해 딜레이를 걸어줌
println("main: Who has survived request cancellation?")
}
/* 실행결과
job1: I run in GlobalScope and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?
*/
위의 실행결과를 확인하면 부모 코루틴이 취소되었기 때문에 자식 코루틴 역시 취소되었다는 것을 볼 수 있다. 이 때 GlobalScope
로 선언된 코루틴은 독립적으로 실행되는 것을 알 수 있다.
Parental responsibilities
부모 코루틴은 join()
을 사용하지 않아도 모든 자식 코루틴이 모두 끝날 때까지 기다린다. 예제를 보면서 확인해보자.
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
// launch a coroutine to process some kind of incoming request
val request = launch {
repeat(3) { i -> // 자식 코루틴 3개 생성
launch {
delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
println("Coroutine $i is done")
}
}
println("request: I'm done and I don't explicitly join my children that are still active")
}
request.join() // 자식 코루틴을 포함해서 request가 끝나길 기다린다
println("Now processing of the request is complete")
}
/* 실행결과
request: I'm done and I don't explicitly join my children that are still active
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Now processing of the request is complete
*/
Combining context elements
코루틴을 사용하다보면 coroutineContext에 여러 개의 요소들을 넘겨주고 싶을 수 있을 것이다. 이럴 때는 미리 overriding 되어있는 +
연산자를 사용해서 전달해주면 된다.
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
launch(Dispatchers.Default + CoroutineName("test")) {
println("I'm working in thread ${Thread.currentThread().name}")
}
}
/* 실행결과
I'm working in thread DefaultDispatcher-worker-1 [DefaultDispatcher-worker-1]
*/
Coroutine scope
예를 들어, 화면 UI를 담당하는 부분에서 코루틴을 사용한다고 가정해보자. 사용자가 해당 화면을 벗어나면 코루틴은 모두 종료되어야한다. 이 때 코루틴이 여러 개가 실행 중이었다면 모두 cancel()
해줘야하기 때문에 번거로울 수 있다. 이는 Coroutine Scope를 활용하여 쉽게 해결할 수 있다.
import kotlinx.coroutines.*
class Activity {
private val mainScope = CoroutineScope(Dispatchers.Default)
fun destory() {
mainScope.cancel()
}
fun doSomething() {
repeat(10) { i ->
mainScope.launch {
delay((i + 1) * 200L)
println("Coroutine $i is done")
}
}
}
}
fun main() = runBlocking<Unit> {
val activity = Activity()
activity.doSomething()
println("Launched coroutines")
delay(500L)
println("Destroying activity!")
activity.destroy()
delay(1000)
}
/* 실행결과
Launched coroutines
Coroutine 0 is done
Coroutine 1 is done
Destroying activity!
*/
Activity 클래스에서 작업하는 모든 코루틴은 모두 mainScope 안에서 생성했다. 그렇게 해서 mainScope.cancel()
을 실행하면 모든 코루틴을 쉽게 종료시킬 수 있다.
'Kotlin' 카테고리의 다른 글
[Kotlin] Principle of Coroutine (0) | 2020.07.05 |
---|---|
[Kotlin] Composing Suspending Functions (0) | 2020.07.05 |
[Kotlin] Cancellation and Timeouts (0) | 2020.07.04 |
[Kotlin] Coroutine Basics (0) | 2020.07.04 |
[Kotlin] Coroutine Guide (0) | 2020.07.04 |