Async 코루틴 빌더 뒤에 await를 호출하지 마세요

async로 비동기 작업을 정의한 뒤, 아무것도 하지 않은 채 연산이 완료되는 걸 기다리는 건 아무 의미가 없습니다.

// X
suspend fun getUser(): User = coroutineScope {
	val user = async { repo.getUser() }.await() // 동기 코드
	user.toUser()
}

// O
suspend fun getUser(): User {
	val user = repo.getUser()
	return user.toUser()
}

비동기 작업 여러 개를 동시에 수행한다면, 마지막 작업을 제외한 모든 작업이 async를 사용해야 합니다. 가독성을 위해 모든 작업에 async를 사용하는 것이 낫습니다.

...
val configDeferred = async { ... }
val newsDeferred = async { ... }
val userDeferred = async { ... }

view.showNews(user.await(), news.await())

withContext(EmptyCoroutineContext) 대신 coroutineScope를 사용하세요

withContext와 coroutineScope의 차이는 withContext가 컨텍스트를 재정의 할 수 있다는 것밖에 없으므로 coroutineScope를 사용하세요.

// 컨텍스트 재정의, 기존 코루틴의 컨텍스트 변경
suspend fun example() {
    withContext(Dispatchers.IO) {
        // IO 스레드에서 실행
        val data = fetchData()
        data
    }
}

// 새로운 코루틴 스코프 생성 (Structured Concurrency 보장)
suspend fun example() {
    coroutineScope {
        // 새로운 코루틴 스코프 생성
        launch { doSomething() }
        async { fetchData() }
    }
}

awaitAll을 사용하세요

suspend fun getUsersData(): List<User> = coroutineScope {
    val deferreds = listOf(
        async { userService.getUser(1) },
        async { userService.getUser(2) },
        async { userService.getUser(3) }
    )
    
    deferreds.awaitAll()
}

중단 함수는 어떤 스레드에서 호출되어도 안전해야 합니다.

중단 함수를 호출할 때, 현재 스레드가 블로킹될까 봐 걱정하면 안 됩니다. 특히 Dispatchers.Main을 자주 사용하는 안드로이드에서 중요하지만, 백엔드에서 동기화를 위해 싱글스레드로 제한된 디스패처를 사용하는 경우에도 마찬가지입니다.

중단 함수가 블로킹 함수를 호출할 때는 Dispatchers.IO나 블로킹에 사용하기로 설계된 커스텀 디스패처를 사용해야 합니다.