코루틴 빌더는 코루틴을 시작하는 방법을 말합니다.
코루틴 빌더의 종류에는 launch, async, runBlocking등이 있습니다.
코루틴을 사용하기 위해 제일 보편적으로 많이 사용하는 빌더로 launch를 사용합니다.
launch에 대한 내용을 살펴보면 다음과 같습니다.
• • •
"Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job."
"By default, the coroutine is immediately scheduled for execution."
context - additional to CoroutineScope.coroutineContext context of the coroutine.
start - coroutine start option. The default value is CoroutineStart.DEFAULT.
block - the coroutine code which will be invoked in the context of the provided scope.
현재 스레드를 차단하지 않고 새로운 코루틴을 시작하며, 코루틴에 대한 참조를 Job으로 반환합니다.
기본적으로 코루틴은 즉시 실행되도록 스케줄링됩니다.
context - CoroutineScope.coroutineContext에 코루틴 context를 추가할 수 있습니다.
start - 코루틴 시작 옵션으로 코루틴을 언제 실행할 지 결정하는 옵션이며 기본값은 DEFAULT 입니다.
block - 해당 스코프내에서 실행될 코루틴 코드 입니다.
• • •
정리하면 launch는 결과값을 반환하지 않는 비동기 작업을 시작하고, 그 작업을 제어할 수 있는 Job 객체를 반환하는 코루틴 빌더 입니다.
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
launch 코루틴 빌더는 기본적으로 context와 start 등으로 실행 환경을 설정할 수 있습니다.
newCoroutineContext(context): 현재 스코프의 컨텍스트와 매개변수 context를 합쳐서 새로운 코루틴 컨텍스트를 생성합니다.
start 옵션에 따라 다른 타입의 코루틴 객체를 생성합니다:
coroutine.start(start, coroutine, block): 생성된 코루틴 객체에 시작 옵션과 실행할 코드 블록을 전달하여 시작을 준비합니다. LAZY가 아닌 경우 즉시 스케줄링됩니다.
최종적으로 생성된 코루틴 객체를 Job으로 반환하여 호출자가 코루틴을 제어할 수 있게 합니다.
코루틴을 시작하는 방법으로 aysnc도 있습니다.
async에 대한 내용도 살펴보겠습니다.
• • •
Creates a coroutine and returns its future result as an implementation of Deferred. The running coroutine is cancelled when the resulting deferred is cancelled.
By default, the coroutine is immediately scheduled for execution.
코루틴을 생성하고 그 결과를 Deferred로 반환합니다. 생성된 Deferred가 취소되면 실행 중인 코루틴도 함께 취소됩니다.
기본적으로 코루틴은 즉시 실행되도록 스케줄링됩니다.
• • •
async 내부 코드 :
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
async는 launch와 같이 코루틴이 즉시 실행되도록 스케줄링되고, 코루틴의 시작 옵션으로 기본으로DEFAULT로 지정됩니다.
하지만 async는 launch와 다르게 Deferred를 반환하여 결괏값을 반환받을 수 있습니다.
async로 만든 작업의 결과를 받으려면 await() 및 awaitAll() 함수를 사용합니다.
fun main() = runBlocking<Unit> {
val deferred = async {
println("작업 중..")
"작업 결과" // 실제 결과값 반환
}
val result = deferred.await() // 결과값 받기
println("결과: $result")
println("작업 끝")
}
// 결과
작업 중..
결과: 작업 결과
작업 끝
async 블록 안의 코드를 즉시 시작하고 Deferred 객체를 반환 합니다.
fun main() = runBlocking<Unit> {
var async1 = "async1"
var async2 = "async2"
val deferred1 = async {
println("async1 작업 시작")
delay(3000)
async1 = "async1 작업 처리"
}.invokeOnCompletion {
println("async1 작업 완료")
}
val deferred2 = async {
println("async2 작업 시작")
delay(500)
async2 = "async2 작업 처리"
}.invokeOnCompletion {
println("async2 작업 완료")
}
println("작업 종료 : $async1, $async2")
}
// 결과
작업 종료 : async1, async2
async1 작업 시작
async2 작업 시작
async2 작업 완료
async1 작업 완료
async의 내부는 비동기적으로 동작 하고 있다는 것을 알 수 있습니다.
하지만 await()를 사용하지 않으면 변수 값이 변경되기 전에 출력되므로, await()를 통해 작업 완료를 기다린 후 결과를 확인할 수 있습니다.
fun main() = runBlocking<Unit> {
var async1 = "async1"
var async2 = "async2"
val deferred1 = async {
println("async1 작업 시작")
delay(3000)
async1 = "async1 작업 완료"
}
val deferred2 = async {
println("async2 작업 시작")
delay(500)
async2 = "async2 작업 완료"
}
deferred1.await()
deferred2.await()
println("작업 종료 : $async1, $async2")
}
// 결과
async1 작업 시작
async2 작업 시작
작업 종료 : async1 작업 완료, async2 작업 완료
runBlocking은 현재 스레드를 차단하면서 코루틴을 실행하는 빌더로, main 함수나 테스트에서 suspend 함수를 호출할 때 사용합니다.
• • •
"Runs a new coroutine and blocks the current thread interruptibly until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
The default CoroutineDispatcher for this builder is an internal implementation of event loop that processes continuations in this blocked thread until the completion of this coroutine.
When CoroutineDispatcher is explicitly specified in the context, then the new coroutine runs in the context of the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another runBlocking, then this invocation uses the outer event loop.
If this blocked thread is interrupted (see Thread.interrupt), then the coroutine job is cancelled and this runBlocking invocation throws InterruptedException.
새로운 코루틴을 실행하고 완료될 때까지 현재 스레드를 중단 가능하게 차단합니다.
이 함수는 코루틴 내부에서 사용해서는 안 됩니다.
이는 일반적인 블로킹 코드와 suspending 스타일로 작성된 라이브러리를 연결하기 위해 설계되었으며, main 함수와 테스트에서 사용됩니다.
이 빌더의 기본 CoroutineDispatcher는 이 코루틴이 완료될 때까지 차단된 스레드에서 continuation들을 처리하는 이벤트 루프의 내부 구현입니다.
CoroutineDispatcher가 컨텍스트에서 명시적으로 지정되면, 현재 스레드가 차단되는 동안 새 코루틴은 지정된 디스패처의 컨텍스트에서 실행됩니다.
지정된 디스패처가 다른 runBlocking의 이벤트 루프인 경우, 이 호출은 외부 이벤트 루프를 사용합니다.
이 차단된 스레드가 중단되면(Thread.interrupt() 참조), 코루틴 작업이 취소되고 이 runBlocking 호출은 InterruptedException을 던집니다.
• • •
runBlocking은 현재 스레드를 차단하기 때문에 일반적인 앱 개발에서는 주의해서 사용해야 합니다.
주로 runBlocking은 다음과 같은 경우에 사용됩니다
테스트 코드나 앱 진입점에서 코루틴 코드 실행이 필요할 때 또는 HTTP 인증 토큰 갱신 등 동기적 처리가 필요한 경우에 사용 됩니다
코루틴 빌더들(launch, async, runBlocking)을 학습하면서 각각의 용도와 차이점을 이해할 수 있었습니다.
launch는 결과가 필요 없는 작업, async는 결과가 필요한 작업, runBlocking은 일반 함수와 코루틴을 연결하는 특수한 상황에서 사용한다는 것을 알았습니다.
다음 시간에는 코루틴 실행하기 위한 옵션으로 context에 대해서 알아보겠습니다.