async — Kotlin의 Coroutine Builder로, 코루틴을 시작하고 비동기 작업의 결과를 얻는 데 Deferred<T> 객체를 반환합니다. async는 여러 태스크를 병렬로 실행하고 await()를 통해 결과를 집계할 수 있습니다. JetBrains 가이드, 2024에 따르면, async는 코루틴에서 병렬 계산을 조직하는 가장 바랄직한 방법입니다.
주요 포인트
async — CoroutineScope의 확장 함수로, 결과를 반환하는 코루틴을 만듭니다. launch와 달리, async는 Deferred<T> — 미래 값을 나타내는 객체를 반환합니다. async로 시작된 코루틴은 다른 코루틴과 동시에 실행되어 계산을 병렬화할 수 있습니다.
async는 비동기 작업의 결과가 후속 처리에 필요할 때 사용됩니다: 여러 소스에서 데이터 로드, 커렉션의 일괄 처리, 여러 API에 대한 병렬 요청 등이 있습니다. Deferred는 즉시 생성되며, 코루틴이 완료된 후에 결과를 사용할 수 있습니다.
import kotlinx.coroutines.*
suspend fun String.delayedValue(): String {
delay(1000L)
return this
}
fun main() = runBlocking {
val deferred: Deferred<String> = async { "Hello".delayedValue() }
println(deferred.await())
}
async의 시그니처는 launch와 유사합니다: 동일한 CoroutineContext, CoroutineStart 매개변수 및 suspend 블록입니다. 차이점은 반환 유형에 있습니다 — Job 대신 Deferred<T>를 반환하며, 여기서 T는 블록의 마지막 표현식이 반환하는 결과 유형입니다.
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
Deferred<T> — Job을 상속받고 await(), getCompleted(), getCompletionExceptionOrNull() 메소드를 추가하는 인터페이스입니다. await()은 suspend 함수로, 코루틴 또는 다른 suspend 함수 내에서만 호출할 수 있습니다. getCompleted()는 블로킹되지 않는 메소드로, 코루틴이 아직 완료되지 않았으면 예외를 발생시킵니다.
async의 주요 장점은 태스크를 병렬로 실행할 수 있다는 점입니다. 각 작업을 순차적으로 기다리는 대신, async는 코루틴을 동시에 시작하여 전체 시간을 가장 긴 작업의 기간으로 줄입니다.
일반적인 시나리오 — 세 개의 병렬 async 요청으로 사용자 프로필, 설정 및 주문 내역을 로드하는 것입니다. 세 개 모두 완료된 후 결과는 하나의 데이터 모델로 결합됩니다.
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
val profile = async { api.getProfile(userId) }
val settings = async { api.getSettings(userId) }
val orders = async { api.getRecentOrders(userId) }
Dashboard(
profile = profile.await(),
settings = settings.await(),
orders = orders.await()
)
}
coroutineScope 함수는 하위 코루틴을 시작하고 결과를 반환하기 전에 완료를 기다립니다. 전체 블록은 순차적으로 아니라 병렬로 실행됩니다.
Deferred는 결과를 다루기 위한 여러 메소드를 지원합니다. await() 외에도 상태 확인, 완료된 값 획득 및 Kotlin Coroutines 유틸리티 함수를 통한 여러 Deferred 집계 방법이 있습니다.
| 메소드 | 설명 | Suspend? |
|---|---|---|
| await() | 결과가 나올 때까지 코루틴을 일시 중단한다 | 예 |
| getCompleted() | 코루틴이 완료되면 결과 반환 (그렇지 않으면 Exception) | 아니오 |
| getCompletionExceptionOrNull() | 오류 시 예외 또는 null 반환 | 아니오 |
| isCompleted | 코루틴이 완료되었는지 확인한다 | 아니오 |
Deferred 목록을 집계하기 위해 awaitAll()이 사용됩니다 — 컬렉션 내의 모든 Deferred가 완료될 때까지 실행을 일시 중단하는 함수입니다. 하나라도 오류로 완료되면 awaitAll()이 예외를 발생시킵니다.
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
return coroutineScope {
val deferreds = requests.map { async { it() } }
deferreds.awaitAll()
}
}
async와 launch 사이의 선택은 코루틴 결과가 필요한지에 따라 달라집니다. launch는 Job을 반환하고 fire-and-forget 작업에 적합하며, async는 Deferred를 반환하고 데이터를 반환하는 태스크에 사용됩니다. 구문과 매개변수 측면에서 빌더는 동일합니다.
중요한 규칙: fire-and-forget에 async를 사용하지 마세요. 코루틴 결과가 필요 없다면 launch를 사용하세요. async는 Deferred를 생성하여 오버헤드를 만들며, 이런 시나리오에서는 필요 없습니다.
Android에서 async는 ViewModel 및 UseCases 내의 병렬 작업에 사용됩니다. 일반적인 시나리오: 모바일 개발에서의 예시, 여러 발전 작업이 있습니다.
class HomeViewModel : ViewModel() {
fun loadHomeScreen() {
viewModelScope.launch {
val userData = async(Dispatchers.IO) { repository.getUserData() }
val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
val notifications = async(Dispatchers.IO) { repository.getNotifications() }
val state = HomeState(
user = userData.await(),
news = newsFeed.await(),
unread = notifications.await()
)
_uiState.update { state }
}
}
}
오류 격리를 위해 async 그룹을 coroutineScope나 supervisorScope에 감싸는 것이 권장됩니다. supervisorScope는 하나가 실패해도 나머지 async 코루틴이 계속되도록 하여 비중요 작업에 유용합니다.
자주 묻는 질문
가능하지만 의미가 없습니다. await() 없는 async는 코루틴을 시작하지만 결과가 손실됩니다. 결과가 필요 없으면 launch를 사용하세요 — Deferred를 만들지 않으며 메모리와 성능이 더 효율적입니다.
async 내의 예외는 Deferred에 저장됩니다. await()을 호출하면 이 예외가 발생합니다. 발생 없이 확인하려면 getCompletionExceptionOrNull()을 사용하세요. coroutineScope를 사용할 때 오류는 모든 하위 코루틴을 취소합니다.
awaitAll() — Iterable<Deferred<T>>의 확장 함수로, 모든 Deferred가 완료될 때까지 코루틴을 일시 중단합니다. 결과 목록을 반환합니다. 하나라도 Deferred가 실패하면 예외를 발생시키고 나머지를 취소합니다.
그러합니다. async는 CoroutineScope의 확장 함수로 스코프에 역이 있습니다. GlobalScope.async는 수명 주기에 역이 없는 코루틴을 만들어 무한히 실행될 수 있습니다. Android에서는 메모리 누수 위험으로 GlobalScope가 권장되지 않습니다.
수는 디스패처에 따라 달라집니다: Dispatchers.IO는 최대 64개의 동시 스레드를 지원하고, Dispatchers.Default는 CPU 코어 수까지 지원합니다. 대규모 작업의 경우 Semaphore 또는 awaitAll과 함께 mapNotNull을 통해 제한된 병렬성을 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.