You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
funmain() = runBlocking {
launch { // A가 다 끝난 뒤, B가 실행된다.
repeat(5) { i ->println("Coroutine A, $i")
}
}
launch {
repeat(5) { i ->println("Coroutine B, $i")
}
}
println("Coroutine Outer")
}
funmain() = runBlocking {
launch {
repeat(5) { i ->println("Coroutine A, $i")
delay(10L) // A가 한번 호출됐다가, 중지된상태에서 B가 모두 출력되고나서 나머지 A들이 출력 된다.
}
}
launch {
repeat(5) { i ->println("Coroutine B, $i")
}
}
println("Coroutine Outer")
}
funmain() = runBlocking {
launch {
repeat(5) { i ->println("Coroutine A, $i")
delay(10L)
}
}
launch {
repeat(5) { i ->println("Coroutine B, $i")
delay(10L) // A와 B가 번갈아 가면서 호출된다.
}
}
println("Coroutine Outer")
}
3. Cancellation and Timeouts
코루틴 취소
코루틴을 정교하게 취소해주는 것은 중요하다.
왜냐하면, 메모리라는 리소스를 차지하기 때문이다.
코루틴 취소하는 방법은 간단하지만, 코루틴 자체가 취소에 대해서 협조적인 상태가 되어야 하는게 중요하다.
funmain() = runBlocking {
var job = launch {
repeat(1000) { i ->println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 1.3초 뒤에 job을 취소println("main: I'm tired of waiting!")
job.cancel()
job.join()
println("main: Now I can quit.")
}
코루틴 취소는 협조적이다.
코루틴이 취소가 되려면 조건이 필요하다. 즉, 협력이 필요하다.
suspend function은 취소가 가능하다.
suspend function을 호출하면 취소됐는지 확인할 수 있다.
yield() 함수를 통해서 정상 취소가 가능하다. (취소가 되면 내부적으로 예외를 던져서 종료시킴)
funmain() = runBlocking {
val startTime =System.currentTimeMillis()
val job = launch(Dispatchers.Default) { // 이 예제는 코루틴 취소에 대해서 협조적이지 않다. (suspend function이 안불렸기 때문)var nextPrintTime = startTime
var i =0while (i <5) {
if (System.currentTimeMillis() >= nextPrintTime) {
// delay(1L) // 이 주석을 지우면 suspend function이 호출되므로 정상적으로 cancel이 된다. // yield() // 위의 delay를 호출하지 않고, yield()를 통해서 취소를 확인 할 수 있다 ( 이 방법이 더 좋다)println("job: I'm sleeping ${i++} ...")
nextPrintTime +=500L
}
}
}
delay(1300L) // 1.3초 뒤 코루틴 취소를 하려고하지만, 잘 안되는 것을 확인할 수 있다. println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
}
복잡한 코루틴 코드를 취소 가능 하도록 만들기
주기적으로 suspend fucntion을 호출한다 (ex: yield)
명시적으로 취소에 대한 상태를 체크한다 (isActive) - 여기에서 사용한 방법
funmain() = runBlocking {
val startTime =System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
// 이 방법은 exception을 던지지 않는다.var nextPrintTime = startTime
var i =0println("isActive $isActive ...")
while (isActive) {
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime +=500L
}
}
println("isActive $isActive ...")
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
}
코루틴을 종료 할 때 리소스 해제 방법
코루틴에서 네트워크를 사용하거나 DB를 쓸 때, 도중에 코루틴이 취소되면 리소스를 닫아주고 종료해야 한다.
funmain() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
// 여기에서 리소스를 종료 해준다. println("job: I'm running finally")
}
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
}
취소가 불가능한 영역을 실행
드문 케이스이다.
캔슬을 실행해서 이미 코루틴이 캔슬된 상태에서 다시 코루틴을 실행하는 상황
withContext(NonCancellable)을 활용한다.
funmain() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
withContext(NonCancellable) { // finally부분에서 다시 코루틴을 사용하기 위함println("job: I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
}
Timeout
코루틴을 다른곳에서 취소하는 것이 아닌, 코루틴을 실행할 때 이 시간이 지난후에 종료하게끔 하는 것이다.
funmain() = runBlocking {
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->println("I'm sleeping $i ...")
delay(500L)
}
"Done"// will get cancelled before it produces this result
}
println("Result is $result")
}
4. Composing Suspending Functions
기본 순차적 코딩
코루틴에서 일반 코드처럼 작성하게되면, 비동기이지만 순차적으로 실행 된다.
funmain() = runBlocking {
val time = measureTimeMillis {
val one = doSomethingUseFulOne()
val two = doSomethingUseFulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
suspendfundoSomethingUseFulOne(): Int {
println("doSomethingUsefulOne - START")
delay(1000L)
println("doSomethingUsefulOne - END")
return13
}
suspendfundoSomethingUseFulTwo(): Int {
println("doSomethingUsefulTwo - START")
delay(1000L)
println("doSomethingUsefulTwo - END")
return29
}
결과
doSomethingUsefulOne - START
doSomethingUsefulOne - END
doSomethingUsefulTwo - START
doSomethingUsefulTwo - END
The answer is 42
Completed in 2011 ms
async를 통한 동시성 처리
바로 위 예제는 1초 걸리는 연산을 2번해서 2초가 걸렸는데, 두 연산의 처리가 의존적이지 않은 상황일 때 동시에 처리하면 더 빠를 것이다.
코루틴 내부에서 순차적으로 코드를 작성하면, 순차적으로 실행이 되는데 이걸 비동기적으로 실행하고 싶으면 명시적으로 async로 콜해야 한다.
async로 1초 짜리 두개의 연산을 실행하면 1초가 걸린다.
await은 async가 끝날때 까지 기다린다.
async는 코루틴 빌더이다.
funmain() = runBlocking {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
// delay(2000L)// println("test..")println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
suspendfundoSomethingUsefulOne(): Int {
println("doSomethingUsefulOne - START")
delay(1000L)
println("doSomethingUsefulOne - END")
return13
}
suspendfundoSomethingUsefulTwo(): Int {
println("doSomethingUsefulTwo - START")
delay(1000L)
println("doSomethingUsefulTwo - END")
return29
}
doSomethingUsefulOne - START
doSomethingUsefulTwo - START
doSomethingUsefulOne - END
doSomethingUsefulTwo - END
The answer is 42
Completed in 1017 ms
async 코루틴을 지연시켜서 실행시키기
CoroutineStart.LAZY로 async를 쓰면, start() 혹은 await()을 콜할 때 실행된다.
funmain() = runBlocking {
val time = measureTimeMillis {
val one = async (start =CoroutineStart.LAZY){ doSomethingUsefulOne3() }
val two = async (start =CoroutineStart.LAZY){ doSomethingUsefulTwo3() }
println("START")
delay(2000L)
one.start()
two.start() // one.start(), two.start()값을 제거하면 one.await()이 완료된 후에 two.await()이 실행되므로 두 연산에 2초가 걸린다println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
suspendfundoSomethingUsefulOne3(): Int {
println("doSomethingUsefulOne - START")
delay(1000L)
println("doSomethingUsefulOne - END")
return13
}
suspendfundoSomethingUsefulTwo3(): Int {
println("doSomethingUsefulTwo - START")
delay(1000L)
println("doSomethingUsefulTwo - END")
return29
}
START
doSomethingUsefulOne - START
doSomethingUsefulTwo - START
doSomethingUsefulOne - END
doSomethingUsefulTwo - END
The answer is 42
Completed in 3022 ms
async-style 함수
위의 async 예제들을 작성하다보면, async를 호출하는 것 자체를 함수로 만들어서 사용하고 싶은 유혹이 생길 수 있는데, 그러면 안된다는 것을 보여준다.
xxxAsync functions 들은 suspend functions이 아니다.
이 스타일은 코틀린 코루틴에서는 안쓰는 것이 좋다
이러한 문제는 structured concurrency를 통해 해결할 수 있다.
funmain() { // 이렇게 쓰면 안된다 라는 예제를 보여준것임. GlobalScope.async를 하게 되면 exception이 발생했음 에도 불구하고 코루틴이 계속 실행되는것을 볼 수 있음try { // GlobalScope로 선언했기 때문에, 이 어플리케이션 종료와는 무관한 async함수들이 되버린다. 그러므로, 이 어플리케이션이 excpetion이 터져서 종료되도 실행되는 것이다. val time = measureTimeMillis {
val one = somethingUsefulOneAsync4()
val two = somethingUsefulTwoAsync4()
println("my exceptions")
throwException("my exceptions")
runBlocking {
println("The answer is ${one.await() + two.await()}")
}
}
println("Completed in $time ms")
} catch (e:Exception) {
}
runBlocking {
delay(100000)
}
}
funsomethingUsefulOneAsync4() =GlobalScope.async {
println("start, somethingUsefulOneAsync")
val res = doSomethingUsefulOne4()
println("end, somethingUsefulOneAsync")
res
}
funsomethingUsefulTwoAsync4() =GlobalScope.async {
println("start, somethingUsefulTwoAsync")
val res = doSomethingUsefulTwo4()
println("end, somethingUsefulTwoAsync")
res
}
suspendfundoSomethingUsefulOne4(): Int {
println("doSomethingUsefulOne - START")
delay(3000L)
println("doSomethingUsefulOne - END")
return13
}
suspendfundoSomethingUsefulTwo4(): Int {
println("doSomethingUsefulTwo - START")
delay(3000L)
println("doSomethingUsefulTwo - END")
return29
}
my exceptions
start, somethingUsefulTwoAsync
start, somethingUsefulOneAsync
doSomethingUsefulTwo - START
doSomethingUsefulOne - START
doSomethingUsefulTwo - END
doSomethingUsefulOne - END
end, somethingUsefulOneAsync
end, somethingUsefulTwoAsync
Structured concurrency with async
funmain() = runBlocking {
try {
val time = measureTimeMillis {
println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")
} catch (e:Exception) {
}
runBlocking {
delay(5000)
}
}
// 바로 이전 예제처럼 아무 곳에서나 쓸 수 있는 것이 아니라, 코루틴 안에서만 사용 가능하다.suspendfunconcurrentSum(): Int= coroutineScope { // coroutineScope를 사용함으로써 exception이 발생하면 전체 코루틴이 취소된다.val one = async { doSomethingUsefulOne5() }
val two = async { doSomethingUsefulTwo5() }
delay(10)
println("Exception")
throwException()
one.await() + two.await()
}
suspendfundoSomethingUsefulOne5(): Int {
println("doSomethingUsefulOne - START")
delay(3000L)
println("doSomethingUsefulOne - END")
return13
}
suspendfundoSomethingUsefulTwo5(): Int {
println("doSomethingUsefulTwo - START")
delay(3000L)
println("doSomethingUsefulTwo - END")
return29
}
funmain() = runBlocking<Unit> {
try {
failedConcurrentSum()
} catch (e:ArithmeticException) {
println("Computation failed with ArithmeticException")
}
}
suspendfunfailedConcurrentSum(): Int= coroutineScope {
val one = async<Int> {
try {
delay(Long.MAX_VALUE)
42
} finally {
println("First child was cancelled")
}
}
val two = async<Int> {
println("Second child throws an exception")
throwArithmeticException()
}
one.await() + two.await()
}
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException
5. Coroutines under the hood
코루틴은 마법이 아니다.
내부적으로 코틀린 컴파일러가 콜백형태로 만들어서 콜백을 해준다. 즉, 일반적인 코드이다.
컴파일해보면, Continuation Pass Style == CPS 스타일로 변경되는 것을 확인할 수 있다.
6. Coroutine Context and Dispatchers
Dispatchers and threads
코루틴은 코루틴 context에서 실행된다.
코루틴 context는 요소들인 job, dispatcher를 설정할 수 있다.
coroutine context는 coroutine dispatcher를 포함한다.
coroutine dispatcher는 어떤 스레드에서 실행될지를 결정한다.
모든 코루틴 빌더들은 optional로 coroutineContext parameter를 가지고 있다.
funmain() = runBlocking<Unit> {
launch { // runblocking와 같은 컨텍스트에서 실행됨(main)println("main runBlocking :"+" I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
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}")
}
newSingleThreadContext("MyOwnThread").use {
launch (it) {
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
newSingleThreadContext: I'm working in thread MyOwnThread
main runBlocking : I'm working in thread main
코루틴 디버깅 방법
JVM option에 -Dkotlinx.coroutines.debug 명령어를 추가 하면 됨
funlog(msg:String) =println("[${Thread.currentThread().name}] $msg")
funmain() = 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] I'm computing a piece of the answer
[main] I'm computing another piece of the answer
[main] The answer is 42
코루틴에서 스레드들 사이를 넘나드는 방법
withContext에 context랑 같이 실행해주면 그 쓰레드에서 실행된다.
funmain() {
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] Started in ctx1
[Ctx2] Working in ctx2
[Ctx1] Back to ctx1
Context안의 Job
코루틴의 잡은 context의 일부분이다.
funmain() = 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}@28c97a5
My job is StandaloneCoroutine{Active}@32a1bec0
My job is DeferredCoroutine{Active}@22927a81
코루틴의 자식들
코루틴 잡들이 계층구조를 갖고 있고, 부모 자식 관계가 있다.
새로운 코루틴이 실행되면 부모 코루틴의 자식이 된다.
단, GlobalScope는 독립적이고, 부모가 존재 하지 않는다.
// 여러 다른 쓰레드를 하나의 코루틴으로 처리funmain() = runBlocking<Unit>() {
val request = launch {
GlobalScope.launch { // 글로벌 스코프라 종료가 안됨. ( main이 아니다.)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?
부모 코루틴의 책임들
부모 코루틴은 자신의 모든 자식 코루틴이 실행이 종료 될 때 까지 기다린다.
단, 직접 tracking할 필요는 없다.
직접 job.join할 필요도 없다.
// 부모 코루틴은 자식코루틴이 끝나는 것을 기다린다.funmain() = runBlocking<Unit>() {
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 thread")
}
println("Now processing of the request is complete")
}
코루틴 컨텍스트 요소들을 합치기
코루틴 하나를 만드는데 기존 디스페처 이름 + 커스텀 이름 을 만들고 싶은 경우
funmain() = runBlocking<Unit>() {
launch(Dispatchers.Default+CoroutineName("test")) {
println("I'm working in thread ${Thread.currentThread().name}")
}
}
I'm working in thread DefaultDispatcher-worker-1 @test#2
코루틴 스코프(Scope)
안드로이드 화면에서 코루틴들이 실행되고 있은 경우, 사용자가 백키를 눌러서 나가게 되면 코루틴들을 다 종료시켜줘야 한다.
안그러면, 메모리 leak이 걸리거나 잘못될 수 있다.
그런데, 모두 종료하려면 모든 잡을 cancel해줘야 한다.
그래서 코루틴의 잡들을 다 가지고 있다가, cancel()해줄수도 있지만 다른 추상적인 방법이 있다.
그 추상적인 방법이 코루틴 스코프이다.
코루틴 스코프에서 모든 코루틴이 실행되게끔 하고, 화면에 나간다고 했을때 코루틴 스코프만 cancel해주면 모든 잡들이 cancel된다.
classActivity {
privateval mainScope =CoroutineScope(Dispatchers.Default)
fundestroy() {
mainScope.cancel()
}
fundoSomething() {
repeat(10) { i ->
mainScope.launch {
delay((i+1) *200L)
println("Coroutine $i is done")
}
}
}
}
funmain() = runBlocking<Unit>() {
val activity =Activity()
activity.doSomething()
println("Launched coroutines")
delay(500L)
println("Destroying activity!")
activity.destroy() // 여기를 주석처리하면 10번 실행이 모두 되는 것을 확인할 수 있다.
delay(5000L)
}
Launched coroutines
Coroutine 0 is done
Coroutine 1 is done
Destroying activity!