본문 바로가기

코루틴

[코루틴] 일시 중단 함수 - suspend

 

일시 중단 함수

1. suspend fun 키워드로 선언되고 함수 내에 일시 중단 지점을 포함하는 함수
2. 일시 중단 지점이 포함된 코드를 재사용이 가능한 단위로 추출
3. 코루틴 내부에서 실행되는 코드의 집합일 뿐 코루틴이 아니다

 

fun main() = runBlocking<Unit> {
    delay(1000L)
    println("Hello World")
    delay(1000L)
    println("Hello World")
}
// 결과
Hello World
Hello World

 

일시 중단 함수를 사용해 위 코드를 수정

suspend fun delayAndPrint() {
    delay(1000L)
    println("Hello World")
}

fun main() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    delayAndPrint()
    delayAndPrint()
    println(getElapsedTime(startTime))
}
// 결과
Hello World
Hello World
2.015 초

 

 

일시 중단 함수를 코루틴처럼 실행하기

코루틴 빌더 내부에 일시 중단 함수 사용

fun main() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    launch { delayAndPrint() }
    launch { delayAndPrint() }
    println(getElapsedTime(startTime))
}
// 결과
0.003초
Hello World
Hello World

suspend fun delayAndPrint() {
    delay(1000L)
    println("Hello World")
}

 

  1. 첫 번째 launch 빌더 내 delayAndPrint 실행 시 1초간 스레드 사용 권한 양보
  2. 두 번째 launch 빌더 내 delayAndPrint 실행 시 1초간 스레드 사용 권한 양보
  3. runBlocking 코루틴이 양보된 코루틴을 사용해 실행
  4. 약 1초 후 delayAndPrint print 출력

 

일시 중단 함수 사용하는 방법

호출 가능 지점 (코루틴 내부 / 일시 중단 함수)

 

코루틴 내부에서 호출

fun main() = runBlocking<Unit> {
    delayAndPrint("Parent Coroutine")
    launch {
        delayAndPrint("Child Coroutine")
    }
}
// 결과
1초 후 Parent Coroutine
1초 후 Child Coroutine

suspend fun delayAndPrint(message: String) {
    delay(1000L)
    println(message)
}

 

runBlocking, launch 빌더 모두 suspend 함수로 이루어져 있다

public actual fun <T> runBlocking(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T { ... }

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job { ... }

 

 

일시 중단 함수에서 호출

fun main() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val result = searchByKeyword("Kancho")
    println(result.joinToString(","))
    println(getElapsedTime(startTime))
}
// 결과
[DB]Kancho1,[DB]Kancho2,[Server]Kancho1,[Server]Kancho2
2.035초

suspend fun searchByKeyword(keyword: String): Array<String> {
    val dbResults = searchFromDB(keyword)
    val serverResults = 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")
}

 

문제점

2개의 일시 중단 함수가 하나의 코루틴에서 순차적 실행

다른 코루틴에서 실행되도록 하려면?

 

일시 중단 함수 내부에서 CoroutineScope 객체에 접근할 수 있어야 한다

 

 

일시 중단 함수에서 코루틴 실행하는 방법

coroutineScope 사용

단점 
Child 코루틴에서 Exception이 발생하면 부모로 전파되어 전체 코루틴 취소
fun main() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val results = searchByKeyword("Kancho")
    println("[결과] ${results.toList()}")
    println(getElapsedTime(startTime))
}
// 결과
[결과] [[DB]Kancho1, [DB]Kancho2, [Server]Kancho1, [Server]Kancho2]
1.022초


// 일시 중단 함수에서 coroutineScope 사용
// searchFromDB, searchFromServer 같이 실행
suspend fun searchByKeyword(keyword: String): Array<String> = coroutineScope {
    val dbResults = async {
        searchFromDB(keyword)
    }
    val serverResults = async {
        searchFromServer(keyword)
    }
    return@coroutineScope arrayOf(*dbResults.await(), *serverResults.await())
}

 

 

supervisorScope 사용

구조화를 깨지 않고 예외 전파 방지
fun main() = runBlocking<Unit> {
    val results = searchByKeyword("Kancho")
    println("[결과] ${results.toList()}")
}
// 결과
dbResultsDeferred 예외 발생
[결과] [[Server]Kancho1, [Server]Kancho2]

suspend fun searchByKeyword(keyword: String): Array<String> = supervisorScope {
    val dbResultsDeferred = async {
        throw Exception("dbResultsDeferred 예외 발생")
        searchFromDB(keyword)
    }
    val serverResultsDeferred = async {
        searchFromServer(keyword)
    }

    val dbResults = try {
        dbResultsDeferred.await()
    } catch (e: Exception) {
        println(e.message)
        arrayOf()
    }

    val serverResults = try {
        serverResultsDeferred.await()
    } catch (e: Exception) {
        arrayOf()
    }
    return@supervisorScope arrayOf(*dbResults, *serverResults)
}

 

 

supervisorScope