일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- mysql 튜닝
- 정보처리기사 실기 기출문제
- IntelliJ
- kotlin
- Spring
- 티스토리챌린지
- kotlin spring
- 기록으로 실력을 쌓자
- Kubernetes
- CKA 기출문제
- Java
- Linux
- kotlin coroutine
- aws
- CloudWatch
- PETERICA
- APM
- AI
- 오블완
- Elasticsearch
- 정보처리기사실기 기출문제
- CKA
- minikube
- MySQL
- 정보처리기사 실기
- kotlin querydsl
- Pinpoint
- 공부
- AWS EKS
- 코틀린 코루틴의 정석
- Today
- Total
피터의 개발이야기
[kotlin] 코틀린 코루틴의 정석 - 일시 중단 함수 본문
ㅁ 들어가며
ㅇ 코틀린 코루틴의 정석 책을 보고 정리한 글입니다.
9장 일시 중단 함수
- 일시중단함수의 개념
- 일시중단함수 사용법
- 일시중단함수에서 코루틴을 실행하는 방법
- 일시중단함수의 호출지점
ㅁ 일시중단함수란?
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
delayAndPrint(startTime)
delayAndPrint(startTime)
}
// 일시 중단 함수 생성
suspend fun delayAndPrint(startTime:Long) {
delay(1000L)
println("[${Thread.currentThread().name}] [${getElapsedTime(startTime)}] 일시 중단 함수 실행")
}
/*
[main @coroutine#1] [지난 시간: 1012ms] 일시 중단 함수 실행
[main @coroutine#1] [지난 시간: 2035ms] 일시 중단 함수 실행
*/
ㅇ 일시중단함수는 suspend fun로 선언되는 함수 내에 일시중단지점을 포함할 수 있는 기능을 한다.
ㅇ 일시중단함수는 코루틴은 아니다. runBlocking 코루틴 빌더함수를 호출하는 부분 외에 코루틴을 생성하는 부분이 없다.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
repeat(2){
launch {
delayAndPrint(startTime)
}
}
println("[${Thread.currentThread().name}] [${getElapsedTime(startTime)}] root 실행")
}
suspend fun delayAndPrint(startTime:Long) {
delay(1000L)
println("[${Thread.currentThread().name}] [${getElapsedTime(startTime)}] 일시 중단 함수 실행")
}
/*
[main @coroutine#1] [지난 시간: 4ms] root 실행
[main @coroutine#2] [지난 시간: 1020ms] 일시 중단 함수 실행
[main @coroutine#3] [지난 시간: 1021ms] 일시 중단 함수 실행
*/
ㅇ 코루틴 빌더 함수인 launch를 호출하여 코투틴을 실행하면 main 스레드가 사용권을 양보하여 코루틴을 병렬로 처리할 수 있다.
ㅁ 일시중단함수의 사용
코루틴 내부에서 일시중단함수 호출하기
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
delayAndPrint(startTime, "1")
launch {
delayAndPrint(startTime, "2-1")
delayAndPrint(startTime, "2-2")
}
launch {
delayAndPrint(startTime, "3-1")
delayAndPrint(startTime, "3-2")
}
}
suspend fun delayAndPrint(startTime:Long, key: String) {
delay(1000L)
println("[${Thread.currentThread().name}] [${getElapsedTime(startTime)}] [${key}] 일시 중단 함수 실행")
}
/*
[main @coroutine#1] [지난 시간: 1008ms] [1] 일시 중단 함수 실행
[main @coroutine#2] [지난 시간: 2027ms] [2-1] 일시 중단 함수 실행
[main @coroutine#3] [지난 시간: 2028ms] [3-1] 일시 중단 함수 실행
[main @coroutine#2] [지난 시간: 3030ms] [2-2] 일시 중단 함수 실행
[main @coroutine#3] [지난 시간: 3030ms] [3-2] 일시 중단 함수 실행
*/
ㅇ 코드 수행 결과를 보면 각 코루틴이 일시 중단 함수를 잘 실행하였다.
일시중단함수 안에서 일시 중단함수 호출하기
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
delayAndPrint(startTime, "1")
launch {
delayAndPrint(startTime, "re1")
}
launch {
delayAndPrint(startTime, "re2")
}
}
// 일시중단함수 안에 일시중단함수 호출하기를 재귀형태로 구현
suspend fun delayAndPrint(startTime:Long, key: String) {
delay(1000L)
println("[${Thread.currentThread().name}] [${getElapsedTime(startTime)}] [${key}] 일시 중단 함수 실행")
if (key.startsWith("re")) delayAndPrint(startTime, "*"+key)
}
/*
[main @coroutine#1] [지난 시간: 1009ms] [1] 일시 중단 함수 실행
[main @coroutine#2] [지난 시간: 2039ms] [re1] 일시 중단 함수 실행
[main @coroutine#3] [지난 시간: 2042ms] [re2] 일시 중단 함수 실행
[main @coroutine#2] [지난 시간: 3045ms] [*re1] 일시 중단 함수 실행
[main @coroutine#3] [지난 시간: 3046ms] [*re2] 일시 중단 함수 실행
*/
ㅇ 일시중단함수 안에서 일시중단함수를 수행하여도 main 스레드가 병렬로 처리하고 있다.
일시중단함수에서 코루틴실행하기
ㅇ 일시중단함수에서 코루틴 빌더 호출 시 같은 coroutine body에 있어야 한다.
ㅇ 더 정확히 말하자면, 제어권이 공유 가능한 서로 같은 CoroutineScope로 묶여야 한다.
ㅇ 그 이유는 일시중단함수 내부에서는 일시 중단 함수를 호출한 코루틴의 CoroutineScope에 접근할 수 없기 때문이다.
ㅇ 그래서 일시중단함수에서 코루틴 빌더 함수인 launch나 async 함수를 호출할 수 있도록 같은 CoroutineScope객체에 접근하도록 해야한다.
coroutineScope 사용해 일시중단함수에서 코루틴 실행하기
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
// 2. 작업
val results = 상위작업("작업")
println("[결과] ${results.toList()}") // 결과
println(getElapsedTime(startTime)) // 지난 시ㅌ간 표시
}
suspend fun 상위작업(keyword: String): Array<String> = coroutineScope { // this: CoroutineScope
val 하위작업1Deferred = async {
하위작업1(keyword)
}
val 하위작업2Deferred = async {
하위작업2(keyword)
}
return@coroutineScope arrayOf(*하위작업1Deferred.await(), *하위작업2Deferred.await())
}
suspend fun 하위작업1(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[하위작업1]${keyword}1", "[하위작업1]${keyword}2")
}
suspend fun 하위작업2(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[하위작업2]${keyword}1", "[하위작업2]${keyword}2")
}
fun getElapsedTime(startTime: Long): String = "지난 시간: ${System.currentTimeMillis() - startTime}ms"
/*
[결과] [[하위작업1]작업1, [하위작업1]작업2, [하위작업2]작업1, [하위작업2]작업2]
지난 시간: 1044ms
*/
ㅇ runBlocking 코루틴에서 상위작업의 일시중단 함수를 호출하면서 내부에서 coroutineScop 아래에 새로운 Job객체를 가진 CoroutineScope가 생성되고, 그 하위작업이 각각 Deferred 하위작업을 구현한다.
// ~~~
suspend fun 상위작업(keyword: String): Array<String> = coroutineScope { // this: CoroutineScope
val 하위작업1Deferred = async {
throw Exception("하위작업1 예외 발생")
하위작업1(keyword)
}
val 하위작업2Deferred = async {
하위작업2(keyword)
}
return@coroutineScope arrayOf(*하위작업1Deferred.await(), *하위작업2Deferred.await())
}
// ~~~
/*
Exception in thread "main" java.lang.Exception: 하위작업1 예외 발생
...
Caused by: java.lang.Exception: 하위작업1 예외 발생
...
*/
ㅇ 여기서 문제점은 하위작업에서 예외가 발생하면 전체가 취소대 버린다.
ㅇ 이를 해결하기 위해서는 supervisorScope가 필요하다.
supervisorScope를 사용해 일시중단함수에서 코루틴 실행
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
// 2. 작업
val results = 상위작업("작업")
println("[결과] ${results.toList()}") // 3. 결과값 출력
println(getElapsedTime(startTime)) // 4. 지난 시간 표시
}
suspend fun 상위작업(keyword: String): Array<String> = supervisorScope { // this: CoroutineScope
val 하위작업1Deferred = async {
throw Exception("하위작업1 예외 발생")
하위작업1(keyword)
}
val 하위작업2Deferred = async {
하위작업2(keyword)
}
// try문 추가하여 예외 대응으로 빈값 리턴
val 하위작업1Results = try {
하위작업1Deferred.await()
} catch (e: Exception) {
arrayOf()
}
// try문 추가하여 예외 대응으로 빈값 리턴
val 하위작업2Results = try {
하위작업2Deferred.await()
} catch (e: Exception) {
arrayOf() // 에러 발생 시 빈 결과 반환
}
return@supervisorScope arrayOf(*하위작업1Results, *하위작업2Results)
}
suspend fun 하위작업1(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[하위작업1]${keyword}1", "[하위작업1]${keyword}2")
}
suspend fun 하위작업2(keyword: String): Array<String> {
delay(1000L)
return arrayOf("[하위작업2]${keyword}1", "[하위작업2]${keyword}2")
}
fun getElapsedTime(startTime: Long): String = "지난 시간: ${System.currentTimeMillis() - startTime}ms"
/*
[결과] [[하위작업2]작업1, [하위작업2]작업2]
지난 시간: 1054ms
*/
ㅁ 함께 보면 좋은 사이트
'Programming > Kotlin' 카테고리의 다른 글
[kotlin] 코틀린 코루틴의 정석 - 비동기 다중 스레드 시 공유 데이터 정합성 문제 (1) | 2024.06.08 |
---|---|
[kotlin] 코틀린 코루틴의 정석 - 코루틴의 이해 (0) | 2024.06.07 |
[kotlin] 코틀린 코루틴의 정석- 예외처리 (0) | 2024.06.04 |
[kotlin] 코틀린 코루틴의 정석- 구조화된 동시성(Structured Concurrency) (0) | 2024.06.03 |
[kotlin] 코틀린 코루틴의 정석- CoroutineContext, 코루틴 실행환경 설정 (0) | 2024.06.02 |