coroutine은 kotlin 언어를 쓰는 개발자들이 겪는 스레딩 문제를 직관적인 방식으로 해결 할 수 있는 기술 이다.
기존 자바의 async task 또는 rxjava 등 비동기 작업을 하기 위해서는 하나의 쓰레드를 생성해야 하는데,Coroutine은 쓰레드 스케쥴링 가능한 코드 블록 또는 코드블록들의 집합내에서 스위칭이 이뤄지기 때문에 OS단의 context switching이 일어나는게 아닌 Coroutine Object 객체들만이 스위칭함으로써 부하가 덜 생긴다.
- Task 단위 = Thread
다수의 작업 각각에 Thread를 할당 한다.- Context Switching
OS커널에 의한 Context Switching을 통해 동시성을 보장한다.
Blocking: 작업 1(Thread) 이 작업 2(Thread) 의 결과가 나오기까지 기다려야한다면
작업 1 Thread 는 Blocking 되어 그 시간동안 해당 자원을 사용하지 못한다.
- Task 단위 = Obejct(Coroutine)
다수의 작업 각각에 Object를 할당 한다.
이 Coroutine Object는 객체를 담는 JVM Heap에 적재 된다.- Progreammer Switching = No Context Switching
프로그래머의 코딩을 통해 Switching 시점을 마음대로 정함으로써 동시성을 보장한다.
Suspend(Non_Blocking) : 작업 1(Object) 이 작업 2(Object) 의 결과가 나오기까지 기다려야한다면 작업 1 Object 는 Suspend 되지만 작업 1 을 수행하던 Thread 는 그대로 유효하기 때문에 작업 2 도 작업 1 과 동일한 Thread 에서 실행될 수 있습니다.
Kotlin 표준 라이브러리에 정의된 CoroutineContext 타입의 값으로 정의된 어떤 context에서 실행된다.
코루틴의 범위, 코루틴 블록을 묶음으로 제어할수 있는 단위
GlobalScope 는 CoroutineScope 의 한 종류입니다. 미리 정의된 방식으로 프로그램 전반에 걸쳐 백그라운드 에서 동작 한다.
코루틴의 실행을 특정 스레드로 제한하거나, 스레드 풀에 보내거나, 제한을 받지 않는 상태로 실행할 수 있다.
Dispatchers.Default : CPU 사용량이 많은 작업에 사용합니다. 주 스레드에서 작업하기에는 너무 긴 작업 들에게 알맞습니다.
Dispatchers.IO : 네트워크, 디스크 사용 할때 사용합니다. 파일 읽고, 쓰고, 소켓을 읽고, 쓰고 작업을 멈추는것에 최적화되어 있습니다.
Dispatchers.Main : 안드로이드의 경우 UI 스레드를 사용합니다.
1. 사용할 Dispatcher 를 결정하고
2. Dispatcher 를 이용해서 CoroutineScope 만들고
3. CoroutineScope 의 launch 또는 async 에 수행할 코드 블록을 넘기면 됩니다.
ex )_다음 예시에서 내부 코루틴 블록은 멈추지 않습니다.
val scope= CoroutineScope(Dispachers.Main)
val job = scope.launch {
//새로운 Scope 생성
CoroutineScope.launch(Dispachers.Main){
// 외부 코루틴이 취소 되어도 끝까지 돌림
}
}
job.cancel()
외부 코루틴 블록 과 내부 코루틴 블록은 서로 제어범위가 달라진다.
Job 객체의 cancel() 메서드는 자신이 해당하는 CoroutineScope 의 코루틴 블록을 취소시켜 멈출수 있지만, 내부 코루틴 블록은 다른 CoroutineScope 로 분리 되었기 때문에 멈출수 없다.
코루틴 블록이 멈춰도, 내부 코루틴 블록은 끝까지 수행 됨
-launch, async
-runBlocking
-Job, Deferred
launch는 job 객체를 반환 한다.
val job : Job = launch {
...
}
반환받은 Job 객체로 코루틴 블록을 취소하거나, 다음 작업의 수행전 코루틴 블록이 완료 되기를 기다릴 수 있다.
val job:Job = CoroutineScope(Dispachers.IO).launch {
var i = 0
while (i < 10) {
delay(500)
i++
}
}
job.join() // 완료 대기
job.cancel() // 취소
여러개의 코루틴 블록을 완료 대기 시킬 수 있다. ( joinAll(job,job) )
val job1: Job = CoroutineScope(Dispatchers.IO).launch {
var i = 0
while (i < 10) {
delay(500)
println("job1 state = ${this.coroutineContext.job} i=$i")
i++
}
}
val job2: Job = CoroutineScope(Dispatchers.IO).launch {
var i = 0
while (i < 10) {
delay(500)
println("job2 state = ${this.coroutineContext.job} i=$i")
i++
}
}
runBlocking {
joinAll(job1, job2)
job1.cancel()
job2.cancel()
println("${job1.job}")
println("${job2.job}")
}
위와 같은 예제로 job1 코루틴 context 객체를 받아서 돌릴 수 있다.
val job1: Job = CoroutineScope(Dispatchers.IO).launch {
var i = 0
while (i < 10) {
delay(500)
println("job1 state = ${this.coroutineContext.job} i=$i")
i++
}
}
CoroutineScope().launch(job1) { //
var i = 0
while (i < 10) {
delay(500)
println("job2 state = ${this.coroutineContext.job} i=$i")
i++
}
}
runBlocking {
job1.join()
}
async() 함수로 시작된 코루틴 블록은 Deferred 객체를 반환한다.
val deferred : Deferred<T> = async{
...
T // 결과값
}
Deferred 객체를 이용해 제어가 가능하며 동시에 코루틴 블록에서 계산된 결과값을 반환 한다.
val deferred: Deferred<Int> = CoroutineScope(Dispatchers.IO).async {
var i = 0
var sum = 0;
while (i < 10) {
sum += i
i++
}
sum //결과값 반환
}
runBlocking {
val msg = deferred.await() //값을 반환
println(msg) // result 출력 45
}
}
async 코루틴 블록을 실행할 경우 각각의 Deferred 객체에 대해서 await() 함수로 코루틴 블록이 완료 될때까지 다음 코드 수행을 대기 할 수 있습니다. await() 함수는 코루틴 블록이 완료되면 결과를 반환
val deferred1 = CoroutineScope(Dispatchers.IO).async {
var i = 0
while (i < 10) {
delay(500)
i++
}
"result1"
}
val deferred2 = CoroutineScope(Dispatchers.IO).async {
var i = 0
while (i < 10) {
delay(1000)
i++
}
"result2"
}
runBlocking {
val result1 = deferred1.await()
val result2 = deferred2.await()
println("$result1 , $result2") // result1 , result 2 출력
}
awaitAll(deferred1,deferred2) //async를 동시에 돌려 결과를 반환 받는다.
코루틴을 블록을 지연 시킬 수 있다.
**CoroutineStart.LAZY**
example :)
- async(context = Dispatchers.IO, start = CoroutineStart.LAZY)
- launch(context = Dispatchers.IO, start = CoroutineStart.LAZY)
start() 함수는 async 코루틴 블록을 실행 시킬땐 블록의 수행 결과를 반환하지 않습니다. 또한 await(),join 함수와 다르게 코루틴 블록이 완료 되는것을 기다리지 않습니다.
start
lazy async 0
lazy async 1
lazy async 2
lazy async 3
lazy async 4
end
start
end
lazy async 0
lazy async 1
lazy async 2
lazy async 3
lazy async 4
runBlocking() 함수는 코드 블록이 작업을 완료 하기를 기다린다.
runBlocking {
...
}
launch() 함수로 시작된 블록은 join() 함수
async() 함수로 시작된 블록은 await() 함수
runBlocking() 함수로 시작된 블록은 아무런 추가 함수 호출 없이 해당 블록이 완료
job을 취소 할 수 있다.
블록내에서 처리 후 cancel()을 호출 할 수 있다.
cancel을 할 경우 coroutine의 모든 suspending functions은 취소를 지원 된다.
runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) {
// computation loop, just wastes CPU //
yield() //여기에 추가하면 정상적으로 취소된다
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
하지만 위 코드는 블록내의 코루틴이 아닌 코틀린 시스템내에서 작동하는 코드 이므로 while문 안의
취소 여부와 관계 없이 동작 한다.
취소 할 수 있는 방법은 두가지가 있다.