반응형
launch
함수의 Job
launch
함수로 시작한Coroutine
은Job
타입의 객체를 Return한다Job
은coroutine
자체를 의미하기에,launch
로 실행되는Coroutine
을 취소할 수도 있고, 기다릴 수도 있다.
→ 즉LifeCycle
에 관심이 있다.
async
함수의 Deferred
- 여기서
Deferred
는Job
을 상속한 클래스이기 때문에,launch
를 사용한 곳에async
를 사용해도 항상 아무 문제가 없다. async
는 return할 결과값이 있는데,Deferred<T>
타입으로 객체를 return한다.Deferred
는Future
처럼 미래의 결과값을 담아놓을 수 있는 객체이며 결국 미래의 결과값을 의미하므로 완료되길 기다리다가 Return되는 값에 관심이 있다.Deferred
는 공식문서에서Non-Blocking Cancellable Future
라고 되어있는데, 내부를 보면Job
을 상속받기에,Job
의 역할도 할 수 있는 것을 알 수 있다. 따라서 이는Job
을 확장해서 가지는 성격이라고 볼 수 있다. 그리고Future
은Return
값을 가지기 때문에Future같은 역할
을 한다.- 즉, 결과값을 가지는 Job이라는 의미에서
Non-Blocking Future
라고 표현된 것이라 볼 수 있다.
- 즉, 결과값을 가지는 Job이라는 의미에서
Job
의 상태
Job
의 상태는 총 6가지로 정의 된다
New
→Active
→Completing
→Completed
Canceling
→Cancelled
.
Job 상태변수
isActive
:Job
이 실행중인지 여부 표시
(coroutineScope
에서의isActive
는 사실coroutineContext[Job]?.isActive == true
의 Shortcut이다.)isCompleted
:Job
의 실행이 완료되었거나,cancel
이 완료되었는지 표시isCancelled
:Job
cancel
이 요청되었는지 여부를 표시
상태 / 상태변수 | isActive | isCompleted | isCancelled |
---|---|---|---|
생성됨 New | false | false | false |
실행 중 Active | true | false | false |
실행 완료 Completing | true | false | false |
취소 중 Cancelling | false | false | true |
취소 완료 Cancelled (최종 상태) | false | true | true |
실행 완료 Completed (최종 상태) | false | true | false |
Job
의 상태는 한 방향으로만 이동한다
Job
은 특정 상태에 도달하면, 이전 상태로 되돌아가지 않는다.
runBlocking {
val job = CoroutineScope.launch {
delay(2000)
}
job.join()
//Restart
job.start()
job.join()
}
- 위 코드에서 처음 호출한
job.join()
이 완료되면,완료됨
상태에 도달했으므로,start()
를 호출해도, 아무런 변화가 없다.
launch
와 async
의 Lazy한 실행 방법 (지연 실행)
Job
과Deferred
은 생성과 동시에 실행하기 때문에, 필요한 위치에 바로 생성해서 실행해야 하는 문제로,Coroutine
실행의 유연성이 떨어질 수 밖에 없다. 이를 해결하기 위해 필요할 때 수행하도록 할 수 있다.Job
이나Deferred
를 미리 만들어두고, 특정 시점에Coroutine
을 실행하도록 하면 된다.Job
을 생성하는launch
나async
메소드에 인자로,start=CoroutineStart.LAZY
를 넣으면 된다Lazy하게 생성된 Job
은 2가지 방법으로 실행 가능하다start()
: 일시 중단 없이 실행되는 메소드- 생성된
Coroutine
작업(Job
)을 일시 중단 없이 실행한다
→ 실행 위치를Coroutine 내부
나suspend function 내부
로 바꾸는 것이 필요치 않다
→suspend function
에서 호출하지 않아도 된다.
- 생성된
join()
:Job
이 완료될때 까지Job
이 실행되고 있는Coroutine
을 일시중단 한다- 실행을 일시중단 할 수 있기 때문에
suspend 함수
내부에서 호출해야 한다
- 실행을 일시중단 할 수 있기 때문에
- 예시
suspend fun main() {
val job = CoroutineScope(Dispatchers.IO).launch(start = CoroutineStart.LAZY) {
println("가나다")
}
job.start()
delay(100L)
}
// delay가 없다면 결과값이 출력되지 않는다
// 문제는 작업에 얼마나 걸릴 지 알수 없기에 delay를 늘리는 것은 잘못된 코딩이라는 것이다.
// 이를 위해 이러한 작업이 끝날 때 까지 일시중단 해주는 join() 메소드를 사용하는 것이다.
suspend fun main() {
val job = CoroutineScope(Dispatchers.IO).launch(start = CoroutineStart.LAZY) {
println("가나다")
}
job.join()
}
// 위와 동일하게 출력된다
Coroutine
시작 지점에 대한 속성
- 위에서
Lazy
이외에 필요한 경우,launch()
나async()
에 인자를 지정해,Coroutine
에 필요한 속성을 줄 수 있다. launch
와async
의 함수의parameter
를 보면 알 수 있듯start
매개변수를 통해 지정해주면 된다.DEFAULT
: 즉시 시작LAZY
: Coroutine을 느리게 시작(처음에는 중단된 상태이며, 함수로 시작 필요)ATOMIC
: 최적화된 방법으로 시작UNDISPATCHED
: 분산 처리 방법으로 시작
Join()
을 활용한 동시성 제어
Job
내부 함수로, 동시성을 제어할 수 있는 함수이다.- 순서를 정해야 하는 경우
join
을 통해 순차적으로 실행되도록 할 수 있다. (순서관리) - →
Coroutine
내부에 여러launch
블럭이 존재하는 경우, 모두 새로운Coroutine
으로 분기되어 동시 실행하기에, 순서를 정할수 없다 - 모든 자식
job
이 완료될 때까지,특정 job
과 연관된Coroutine
을 정지한다 - 이러한 구조들이
Structured Concurrency
의 기반을 마련한다. - → 각
Coroutine
의 참조를 직접 관리할 필요 없이,Coroutine
이 더이상 오래 실행되지 않게 할수 있다는 뜻이다. - 예시
CoroutineScope(Dispatchers.Default).launch {
launch {
for (i in 0..5){
delay(500)
Log.d("코루틴", "$i")
}
}.join()
launch {
for (i in 6..10){
delay(500)
Log.d("코루틴", "$i")
}
}
}
- 예시 2
val job = launch {
repeat(1_000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancel()
job.join() // job 이 완료될 때까지 기다림
println("main: Now I can quit.")
launch
블럭은job Type
을Return
job
은 실행중인Coroutine
을 취소할 수 있다.Job
의 취소는 아래에서 추가적으로 다룬다.
job.cancel()
+job.join()
=job.cancelAndJoin()
cancelAndJoin()
은특정 Job
을 취소한다
Job
을 활용해 여러 개의 Coroutine
을 한번에 관리하는 방법
Job
은coroutine
자체를 의미한다.Coroutine
에 대한Job
객체를 반환받아,Coroutine
을 제어할 수 있다.- 그렇다면 한
CoroutineScope
내에 여러 개의Coroutine
이 존재하고,
그Coroutine
을 한 번에 관리하기 위해서는?Job
또한CoroutineContext
의 일종이기 때문에 이를 이용한다.Job
은coroutineContext[Job]
을 통해 얻어올 수 있다.coroutineContext
는coroutineScope
에서 바로 접근 가능하다.- 하나의
Job
객체를 선언한뒤, 새로 생성되는CoroutineScope
에서 객체를 초기화하면,
이CoroutineScope
의 자식들까지 모두 영향을 받는 Job으로 활용이 가능하다.
fun main() = runBlocking {
val job = Job()
CoroutineScope(Dispatchers.Default + job).launch {
launch {
println("coroutine1 start")
delay(1000) println("coroutine1 end")
}
launch {
println("coroutine2 start")
delay(1000)
println("coroutine2 end")
}
}
delay(300)
job.cancel()
delay(1000)
println("all done !!!")
}
부모 Job
과 자식 Job
Coroutine
의LifeCycle
를Job
으로 관리할 수 있고 (Scope
와Coroutine
의 생명주기 지정가능)- 내부에서 다시
builder
를 호출하면 자식 Job으로 생성된다.- 부모 Job 취소시, 자식 Job도 취소되며, 그 반대는 취소되지 않는다.
launch
로 생성한 자식 Job은 예외 발생시 부모 Job을 취소시킨다async
로 생성한 자식 Job은 예외가 발생해도 부모 Job이 취소되지 않는데, 이는 반환 결과에 예외도 포함시켜버리기 때문이다.- 자식이 Exception을 뱉어버리면 전역으로 퍼지게 되어 일을 중단하게 되는데,
Exception이 발생하게 될 때, 부모에게는 퍼지지않게 하기위해,SupervisorJob()
을 사용하게 된다. 그렇다면, 자식들이 취소되어도 다른 자식들은 이어서 일을 진행할 수 있다.
- 자식이 Exception을 뱉어버리면 전역으로 퍼지게 되어 일을 중단하게 되는데,
Job
의 취소
Job
은 항상 실행에 성공하여실행 완료
상태가 되지 않는다. 다양한 변수로 인해 중간에 취소되어야 할 수 있다.- 예를 들어, 서버와 클라이언트간의 통신의 경우 클라이언트가 서버에 요청하였으나 응답을 지속적으로 받지 못하는 경우, 일정 시간 이후
Job
을 취소하는 과정이 필요하다.
- 예를 들어, 서버와 클라이언트간의 통신의 경우 클라이언트가 서버에 요청하였으나 응답을 지속적으로 받지 못하는 경우, 일정 시간 이후
- 이러한
Job
의 취소의 경우에는 이후의Coroutine의 취소
에서 다루도록 한다.
반응형
'IT > Kotlin' 카테고리의 다른 글
[Kotlin/Coroutine] 5. Coroutine의 예외처리와 취소 (0) | 2023.05.08 |
---|---|
[Kotlin/Coroutine] 4. Coroutine의 구성요소: Coroutine의 생성과 활용 (0) | 2023.04.22 |
[Kotlin/Coroutine] 2. Kotlin에서의 Coroutine: launch와 async (0) | 2023.03.24 |
[Kotlin/Coroutine] 1. Coroutine이란 무엇인가 (0) | 2023.03.16 |
[Kotlin] Delegate Pattern (0) | 2023.02.07 |