일시 중단 함수는 일시 중단을 할 수 있는 곳에서만 호출할 수 있다.
1. 코루틴 내부
2. 일시 중단 함수 (자기자신)
fun main() = runBlocking<Unit> {
delayAndPrint(keyword = "부모 코루틴이 실행 중입니다.")
launch {
delayAndPrint(keyword = "자식 코루틴이 실행 중입니다.")
}
}
suspend fun delayAndPrint(keyword: String) {
delay(1000L)
println(keyword)
}

delayAndPrint()를 runBlocking 코루틴, launch 코루틴에서 각각 실행하였다.
코루틴 내부에서 일시 중단 함수를 호출할 수 있다.
일시 중단 함수는 CoroutineScope에 접근할 수 없다.
따라서 별도 처리 없이 코루틴 빌더 함수를 호출할 수 없다.
이에 대한 이해를 위해 코드를 3단계로 업그레이드 할 것이다.
suspend fun searchByKeyword(keyword: String): Array<String> {
val dbResultsDeferred = searchFromDB(keyword)
val serverResultsDeferred = searchFromServer(keyword)
return arrayOf(*dbResults, *serverResults)
}
suspend fun searchFromDB(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[DB]${keyword}1", "[DB]${keyword}2")
}
suspend fun searchFromServer(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[Server]${keyword}1", "[Server]${keyword}2")
}
suspend fun searchByKeyword(keyword: String): Array<String> {
val dbResultsDeferred = async {
searchFromDB(keyword)
}
val serverResultsDeferred = async {
searchFromServer(keyword)
}
}
위 코드에서는 일시중단 함수인 searchByKeyword()에서 2개의 일시중단 함수를 호출했다.
일시 중단 함수에서 일시 중단 함수 호출할 수 있기 때문이다!
이 코드는 에러가 나지 않고 잘 작동한다.
하지만 이 코드는 문제점을 갖고있다.
바로 "비동기적으로 작동하지 않는다"는 것이다.
현재 DB와 서버에서 검색하는 것이 순차적으로 이루어지고 있는데, 굳이 그럴 필요 없이 같이 병렬적으로 수행해도 무방한 로직이다.
따라서 여기에 비동기 프로그래밍 로직을 추가하자.
suspend fun searchByKeyword(keyword: String): Array<String> {
// 여기에 async 추가하였음
val dbResultsDeferred = async {
searchFromDB(keyword)
}
val serverResultsDeferred = async {
searchFromServer(keyword)
}
}
suspend fun searchFromDB(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[DB]${keyword}1", "[DB]${keyword}2")
}
suspend fun searchFromServer(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[Server]${keyword}1", "[Server]${keyword}2")
}
위 코드에서 에러가 발생하는 이유는 일시 중단 함수는 CoroutineScope에 직접 접근할 수 없기 때문이다.
async는 CoroutineScope의 확장 함수로 정의되어 있어서, CoroutineScope 없이 async를 호출할 수 없다.
일시 중단 함수 내부에서는 코루틴 빌더를 직접 사용할 수 없으므로, 별도의 CoroutineScope를 생성해야 한다.
suspend fun searchByKeyword(keyword: String): Array<String> = coroutineScope {
val dbResultsDeferred = async {
searchFromDB(keyword)
}
val serverResultsDeferred = async {
searchFromServer(keyword)
}
return@coroutineScope arrayOf(*dbResultsDeferred.await(), *serverResultsDeferred.await())
}
suspend fun searchFromDB(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[DB]${keyword}1", "[DB]${keyword}2")
}
suspend fun searchFromServer(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[Server]${keyword}1", "[Server]${keyword}2")
}
coroutineScope을 사용하면 일시중단 함수 내부에 coroutineScope 객체를 만들 수 있다.
coroutineScope 함수는 구조화를 깨지 않는 coroutineScope 객체를 생성하고, 자신의 람다식 내부에서 이 coroutineScope에 접근할 수 있게 해준다.
따라서 coroutineScope을 호출하고 이 람다식 내부에서 async 함수를 호출할 수 있다.
이 코드는 async 함수 2개를 호출하여, 일시중단 함수 내부에서 코루틴 2개를 생성했다.

이 코드의 구조화는 다음과 같다.
최상위 job은 runBlocking이고, searchByKeyword()를 통해 coroutineJob이 생성된다.
이 코루틴 scope 내부에서는 DBResultsDeferred와 serverResultsDeferred가 생성된다.
즉, coroutineScope 함수는 기존 코루틴과의 구조화를 깨지않고 코루틴을 추가한다.
예제를 통해 일시중단 함수 내부에서 코루틴을 실행할 수 있다.
하지만 이렇게 코드를 작성하면 한 가지 문제가 발생할 수 있다.

DB를 읽는 과정에서 예외가 발생하면, 프로그램이 강제종료된다.
예외가 발생하더라도 예외처리를 해주어서 강제종료 되지 않도록 코드를 짜야한다.
만약 이게 실제 서비스였다면 사용자들은 앱이 갑자기 중단되는 최악의 경험을 했어야 할 것이다.

이러한 문제점을 해결하기 위해 supervisorScope을 생성하면된다.
supervisorScope은 coroutineScope과 비슷하게 동작하지만, 내부에 Job 대신 SupervisorJob이 설정 되는 것만 다르다.
• 일시 중단 함수란 suspend fun 키워드로 선언되는 함수로 함수 내에 일시 중단 지점을 포함할 수 있는 함수이다.
• 일시 중단 함수는 코드 블록일 뿐 코루틴이 아니다. 호출되더라도 새로운 코루틴을 실행하지 않는다.
• 일시 중단 함수를 새로운 코루틴에서 실행하려면 호출부를 CoroutineScope()로 감싸야 한다.
• 일시 중단 함수는 일시 중단을 할 수 있는 곳에서만 호출할 수 있다. 즉, 코루틴과 일시 중단 함수에서 호출 할 수 있다.
• 일시 중단 함수 내부에서 코루틴 빌더를 호출하기 위해 CoroutineScope이 필요한데, 일시 중단 함수 자체는 CoroutineScope을 제공하지 않는다.
• 일시 중단 함수 내부에서 coroutineScope 함수를 호출해 구조화를 깨지 않는 CoroutineScope을 만들 수 있다.
• supervisorScope은 coroutineScope과 비슷하게 동작하지만, 내부에 Job 대신 SupervisorJob이 설정 되는 것만 다르다
강의자료 출처 및 참고한 강의:
코틀린 코루틴 완전 정복 by 조세영님
https://www.inflearn.com/course/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%99%84%EC%A0%84-%EC%A0%95%EB%B3%B5/dashboard