안녕하세요 유초코입니다 :)
안드로이드에서 사용하는 비동기 라이브러리는 rx와 coroutine을 꼽을 수 있는데요.
coroutine은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다. Android에서 코루틴은 main 스레드를 차단하여 앱이 응답하지 않게 만들 수도 있는 long-running tasks 를 관리하는 데 도움이 됩니다.
이번 글에서는 Kotlin 코루틴을 사용하여 코드를 작성하는 방법을 살펴보려 합니다!
import kotlinx.coroutines. *
fun main () {
GlobalScope .launch { //백그라운드에서 새 코 루틴을 시작
delay ( 1000L ) // 1초 지연 (기본 시간 단위는 ms)
println ( " World! " ) // 지연 이후 print
}
println ( " Hello, " ) // 코루틴이 지연되는 동안 메인 스레드는 계속된다
Thread .sleep ( 2000L ) // JVM을 유지하기 위해 2초동안 메인 스레드를 차단
println("sleep2000")
}
백그라운드에서 새 코루틴을 시작하고 1초 지연 (기본 시간 단위는 ms) 딜레이를 가진 후에 “World”를 출력합니다. 만약 Thread .sleep ( 2000L)
을 해주지 않게 된다면 main 함수의 실행을 종료하기 때문에 코루틴 함수를 실행하지 않고 종료하게 됩니다.
Hello,
World!
sleep2000
GlobalScope.launch { ... }
와 delay(...)
는
thread { ... }
,Thread.sleep(...)
의 쓰임과 같다고 볼 수 있습니다.
GlobalScope.launch {
delay(1000L)
println("World!")
}
thread {
Thread.sleep(1000)
println("thread!")
}
GlobalScope : CoroutineScope 의 한 종류로 전체 어플리케이션 수명 동안에 작동하고, 취소되지 않는 최상위 수준의 동시 처리를 시작하는데 사용
응용 프로그램 코드는 일반적으로 응용 프로그램 정의 CoroutineScope를 사용해야하며 , GlobalScope 인스턴스에서 비동기 또는 시작 을 사용하는 것은 매우 권장되지 않습니다.
- Dispatchers.Default : 디스패처 또는 기타 ContinuationInterceptor 가 컨텍스트에 지정 되지 않은 경우 모든 표준 빌더에서 사용됩니다 . 공유 백그라운드 스레드의 공통 풀을 사용합니다. 이는 CPU 사용량이 많은 작업에 적합하다.
- Dispatchers.IO — 요청시 생성 된 스레드의 공유 풀을 사용하며 IO 작업이나 작업을 차단하는 것에 적합합니다.
- Dispatchers.Main : 응용 프로그램 “Main”또는 “UI”스레드로 제한되고 코루틴이 이미 올바른 컨텍스트에있을 때 즉시 실행하는 디스패처를 반환, 안드로이드의 경우 UI 스레드를 사용
- Dispatchers.Unconfined — 첫 번째 중단까지 현재 호출 프레임에서 코 루틴 실행을 시작합니다. 이때 코 루틴 빌더 함수가 반환됩니다. 코 루틴은 특정 스레드 또는 풀로 제한하지 않고 해당 일시 중단 함수에서 사용하는 스레드에서 나중에 다시 시작됩니다. 디스패처는 일반적으로 코드에서 사용할 수 없음 .
unconfined dispatcher는 코루틴의 일부 작업을 즉시 수행해야하기 때문에 나중에 실행하기 위해 코 루틴을 디스패치 할 필요가 없거나 원하지 않는 부작용을 생성하는 특정코너 경우에 도움이 될 수있는 고급 메커니즘이다. 제한되지 않은 디스패처는 일반 코드에서 사용해서는 안된다.
- newSingleThreadContext 는 코루틴이 실행할 스레드를 생성합니. 전용 스레드는 매우 비싼 리소스로 실제 애플리케이션에서는 더 이상 필요하지 않을 때 닫기 함수를 사용하여 해제 하거나 최상위 변수에 저장하고 애플리케이션 전체에서 재사용해야합니다.
Dispatcher example
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
main runBlocking : I'm working in thread main
Unconfined : I'm working in thread main
Default : I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread
launch { ... }
의 경우 파라미터없이 사용되며, 실행 중인 Coroutine Scope에서 컨텍스트(따라서 디스패처)를 상속한다. 이 경우 주 스레드에서 실행되는 main runBlocking
coroutine 의 컨텍스트를 상속합니.main
스레드 에서 실행되는 것처럼 보이는 특수 디스패처 이지만 실제 메커니즘은 아래서 다룹니다.launch(Dispatchers.Default) { ... }
에서 GlobalScope.launch { ... }
와 동일한 발송자를 사용.Dispatchers.Unconfined example
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
delay(500)
println("Unconfined : After delay in thread ${Thread.currentThread().name}")
}
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
delay(1000)
println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
}
Unconfined : I'm working in thread main
main runBlocking: I'm working in thread main
Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor
main runBlocking: After delay in thread main
단, 이 코루틴이 suspend되었다가 상태가 재시작 되면 적절한 thread에 재할당되어 시작된다
특정 스레드로 지정되어 처리되어야 하는경우에는 사용하지 않는다.
unconfined dispatcher는 코루틴의 일부 작업을 즉시 수행해야하기 때문에 나중에 실행하기 위해 코 루틴을 디스패치 할 필요가 없거나 원하지 않는 부작용을 생성하는 특정코너 경우에 도움이 될 수있는 고급 메커니즘이다. 제한되지 않은 디스패처는 일반 코드에서 사용해서는 안된다.
launch
는 새 코루틴을 시작하고 호출자에게 결과를 반환하지 않는다.
‘실행 후 삭제’로 간주되는 모든 작업은 launch
를 사용하여 시작할 수 있다.async
는 새 코루틴을 시작하고 await
라는 정지 함수로 결과를 반환하도록 허용한다.보통 일반 함수는
await
를 호출할 수 없으므로 일반 함수에서 새 코루틴을launch
해야 합니다.async
는 다른 코루틴 내부에서만 사용하거나 suspend 함수 내에서 병렬 분해를 실행할 때 사용합니다.
경고:
launch
와async
는 예외를 서로 다르게 처리합니다.async
는 최종await
호출을 예상하므로 예외를 보유하고await
호출의 일부로 예외를 다시 발생시킵니다. 즉,await
를 사용하여 일반 함수에서 새 코루틴을 시작하는 경우 예외를 자동으로 삭제할 수 있습니다. 이렇게 삭제된 예외는 비정상 종료 측정항목에 나타나지 않거나 logcat에 기록되지 않습니다. 자세한 내용은 코루틴의 취소 및 예외를 참조하세요.
val job : Job = launch { ... }
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
coroutineContext 의
Job
인스턴스는 코루틴 자체를 나타낸다.
//1
val job = GlobalScope.launch { ... }
//2
val job = Job()
CoroutineScope(Dispatchers.Default + job).launch { ... }
val job1 = launch {
println("Job1 one scope for start")
var i = 0
for (index in 0..5) {
delay(100)
println("Job1 one scope $i ")
}
}
val job2 = launch {
println("Job2 two scope for start")
for (index in 0..2) {
println("Job2 one scope index $index")
delay(100)
}
}
// job1.join()
// job2.join()
joinAll(job1, job2)
Job1 one scope for start
Job2 two scope for start
Job2 one scope index 0
Job2 one scope index 1
Job1 one scope 0
Job2 one scope index 2
Job1 one scope 1
Job1 one scope 2
Job1 one scope 3
Job1 one scope 4
Job1 one scope 5
val job1 = GlobalScope.launch {
println("Job1 one scope for start")
var i = 0
for (index in 0..5) {
delay(100)
println("Job1 one scope $index ")
}
}
launch (job1){
println("Job2 two scope for start")
for (index in 0..2) {
println("Job2 one scope index $index")
delay(100)
}
}
job1.join()
async() 함수로 시작된 코루틴 블록은 Deferred 객체를 반환
val deferred : Deferred<T> = async {...}
val deferred : Deferred<String> = async {
var i = 0
for (index in 0..5) {
println("async index $index")
delay(100)
}
"결과를 반환"
}
val msg = deferred.await()
println(msg) // result 출력
val deferred : Deferred<String> = async {
var i = 0
for (index in 0..5) {
println("async1 index $index")
delay(100)
}
"결과 1"
}
async(deferred) {
var i = 0
for (index in 0..5) {
println("async2 index $index")
delay(100)
}
"결과 2"
}
val msg = deferred.await()
println(msg) // 결과 1 출력
코루틴은 launch 나 async 메소드가 실행되면 바로 시작되지만 LAZY 를 사용해 처리시점을 지연시킬 수 있다.
각 코루틴 블록 함수의 start 인자에 CoroutineStart.LAZY 를 사용하면 해당 코루틴 블록은 지연 된다.
val job = launch (start = CoroutineStart.LAZY) {...}
또는
val deferred = async (start = CoroutineStart.LAZY) {...}
job.start()또는job.join()
함수 를 호출하는 시점에 launch 코드 블록이 실행된다.async 코루틴 블록을 지연 실행 시킬 경우 Deferred 클래스의 deferred.start()또는deferred.await()
함수를 호출하는 시점에 async 코드 블록이 실행된다.
지연된 async 코루틴 블록 의 경우 start() 함수는 async 코루틴 블록을 실행 시키지만 블록의 수행 결과를 반환하지 않는다. 또한 await() 함수와 다르게 코루틴 블록이 완료 되는것을 기다리지도 않는다.
deferred.await()
함수를 사용해 async 코드 블록이 실행println("stat")
val deferred= async (start = CoroutineStart.LAZY){
var i = 0
for (index in 0..5) {
println("async1 index $index")
delay(100)
}
}
deferred.await()
println("end")
stat
async1 index 0
async1 index 1
async1 index 2
async1 index 3
async1 index 4
async1 index 5
end
deferred.start()
함수를 사용해 async 코드 블록이 실행 println("stat")
val deferred= async (start = CoroutineStart.LAZY){
var i = 0
for (index in 0..5) {
println("async1 index $index")
delay(100)
}
}
deferred.start()
println("end")
stat
end
async1 index 0
async1 index 1
async1 index 2
async1 index 3
async1 index 4
async1 index 5
runBlocking() 함수는 코드 블록이 작업을 완료할 때 까지 기다린다.
runBlocking {
...
}
단순히 join 같은 메소드 기능이 아닌 현재 thread 를 block 하고 실행되는 코드이므로 메인 thread 사용에 주의해야한다.
안드로이드 의 경우 runBlocking() 함수를 메인 thread (UI thread) 에서 호출하여 시간이 오래 걸리는 작업을 수행하는 경우 ANR 이 발생할 위험이 있으므로 주의해야하며 일반적인 Blocking 코드와 suspend 스타일로 적힌 라이브러리들을 bridge해줄 모적으로 설계된 함수로 메인 함수나 테스트에서 사용하는 것이 바람직하다.
하지만 runBlocking() 함수로 시작된 블록은 아무런 추가 함수 호출 없이 해당 블록이 완료될때까지 기다릴수 있습니다.
주의해야 할것은 runBlocking 코루틴 블록이 사용하는 스레드는 현재 runBlocking() 함수가 호출된 스레드가 된다는 것입니다.
https://github.com/Kotlin/kotlinx.coroutines https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html