반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[코루틴의 정석] 9장. 일시 중단 함수 본문

Kotlin/코루틴의 정석

[코루틴의 정석] 9장. 일시 중단 함수

어리둥절범고래 2024. 9. 20. 01:06
반응형

9.1. 일시 중단 함수와 코루틴


9.1.1. 일시 중단 함수란 무엇인가?

 

일시 중단 함수는 suspend fun 키워드로 선언되는 함수로 주로 코루틴의 비동기 작업과 관련된 코드들을 구조화 하고 재사용할 수 있는 코드의 집합으로 만드는데 사용된다.

fun main() = runBlocking<Unit> {
  delayAndPrintHelloWorld()
  delayAndPrintHelloWorld()
}

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


9.1.2. 일시 중단 함수는 코루틴이 아니다

 

일시 중단 함수는 코루틴 내부에서 실행되는 코드의 집합일 뿐, 코루틴이 아니다.

fun main() = runBlocking<Unit> {
  val startTime = System.currentTimeMillis()
  delayAndPrintHelloWorld()
  delayAndPrintHelloWorld()
  println(getElapsedTime(startTime))
}

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

/*
// 결과:
Hello World
Hello World
지난 시간: 2017ms
*/


9.1.3. 일시 중단 함수를 별도의 코루틴상에서 실행하기

 

만약 일시 중단 함수를 코루틴처럼 사용하고 싶다면 일시 중단 함수를 코루틴 빌더로 감싸면 된다.

fun main() = runBlocking<Unit> {
  val startTime = System.currentTimeMillis()
  launch {
    delayAndPrintHelloWorld()
  }
  launch {
    delayAndPrintHelloWorld()
  }
  println(getElapsedTime(startTime))
}

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


/*
// 결과:
지난 시간: 3ms
Hello World
Hello World
*/

1초 정도가 지나고 나서 Hello World가 두 번 출력된다.

 


9.2. 일시 중단 함수의 사용


9.2.1. 일시 중단 함수의 호출 가능 지점

 

일시 중단 함수는 일시 중단을 할 수 있는 곳에서만 호출할 수 있으며, 일시 중단이 가능한 지점은 다음 두 가지이다.

 

  1. 코루틴 내부
  2. 일시 중단 함수


9.2.1.1. 코루틴 내부에서 일시 중단 함수 호출하기

 

코루틴은 언제든지 일시 중단 함수를 호출할 수 있다.

fun main() = runBlocking<Unit> {
  delayAndPrint(keyword = "I'm Parent Coroutine")
  launch {
    delayAndPrint(keyword = "I'm Child Coroutine")
  }
}

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

/*
// 결과:
I'm Parent Coroutine
I'm Child Coroutine
*/


9.2.1.2. 일시 중단 함수에서 다른 일시 중단 함수 호출하기

 

일시 중단 함수 내부에서 일시 중단 함수를 호출할 수 있다.

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")
}

 


9.2.2. 일시 중단 함수에서 코루틴 실행하기

 

9.2.2.1. 일시 중단 함수에서 코루틴 빌더 호출 시 생기는 문제

 

코루틴 빌더 함수는 CoroutineScope의 확장 함수로 선언돼 있기 때문에 일시 중단 함수에서 사용할 수 없다.

 


9.2.2.2. coroutineScope 사용해 일시 중단 함수에서 코루틴 실행하기


일시 중단 함수에서 코루틴 빌더 함수를 사용하려면, coroutineScope를 사용하면 된다.

public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R

coroutineScope 일시 중단 함수를 사용하면 일시 중단 함수 내부에 새로운 CoroutineScope 객체를 생성할 수 있다.

fun main() = runBlocking<Unit> {
  val startTime = System.currentTimeMillis() // 1. 시작 시간 기록
  val results = searchByKeyword("Keyword") // 2. 검색 실행 및 결과 값 반환 받기
  println("[결과] ${results.toList()}") // 3. 결과값 출력
  println(getElapsedTime(startTime)) // 4. 지난 시간 표시
}

suspend fun searchByKeyword(keyword: String): Array<String> = coroutineScope { // this: 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")
}

fun getElapsedTime(startTime: Long): String = "지난 시간: ${System.currentTimeMillis() - startTime}ms"

/*
// 결과:
[결과] [[DB]Keyword1, [DB]Keyword2, [Server]Keyword1, [Server]Keyword2]
지난 시간: 1039ms
*/

 


9.2.2.3. supervisorScope 사용해 일시 중단 함수에서 코루틴 실행하기

supervisorScope 일시 중단 함수는 Job 대신 SupervisorJob 객체를 생성한다는 점을 제외하고는 coroutineScope 일시 중단 함수와 같이 동작한다.

fun main() = runBlocking<Unit> {
  println("[결과] ${searchByKeyword("Keyword").toList()}")
}
/*
// 결과:
[결과] [[Server]Keyword1, [Server]Keyword2]
*/

suspend fun searchByKeyword(keyword: String): Array<String> = supervisorScope { // this: CoroutineScope
  val dbResultsDeferred = async {
    throw Exception("dbResultsDeferred에서 예외가 발생했습니다")
    searchFromDB(keyword)
  }
  val serverResultsDeferred = async {
    searchFromServer(keyword)
  }

  val dbResults = try {
    dbResultsDeferred.await()
  } catch (e: Exception) {
    arrayOf() // 예외 발생 시 빈 결과 반환
  }

  val serverResults = try {
    serverResultsDeferred.await()
  } catch (e: Exception) {
    arrayOf() // 에러 발생 시 빈 결과 반환
  }

  return@supervisorScope 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")
}

 


요약

 

  1. 일시 중단 함수는 suspend fun 키워드로 선언되며, 일시 중단 지점이 포힘된 코드를 재사용이 가능한 단위로 만들어 구조화하는 데 사용된다.
  2. 일시 중단 함수는 코루틴이 아니며, 일시 중단 지점을 포함할 수 있는 코드의 집합일 뿐이다.
  3. 일시 중단 함수가 일반 함수와 다른 점은 일시 중단 지점을 포함한다는 것이다.
  4. 일시 중단 함수는 코루틴이나 다른 일시 중단 함수 내부 등 일시 중단이 가능한 지점에서만 호출될 수 있다.
  5. 일시 중단 함수 내부에서 coroutineScope 함수를 사용해 코루틴의 구조화를 깨지 않는 새로운 CoroutineScope 객체를 생성할 수 있다.
  6. coroutineScope 함수를 사용해 만든 CoroutineScope 객체를 사용해 launch나 async 같은 코루틴 빌더를 호출할 수 있다. 이를 사용하면 일시 중 단 함수 내부에서 비동기 작업을 병렬로 실행할 수 있다.
  7. coroutineScope 함수 대신 supervisorScope 함수를 사용해 일시 중단 함수 내부에서 생성된 코루틴의 예외 전파를 제한할 수 있다.
반응형