일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- cancellationException
- 백준2309
- 백준
- collectLatest
- Flow
- hotStream
- Product Flavor
- TOSS 과제
- Android
- Algorithm
- java
- google play console
- Next Challenge
- coroutinecontext
- withContext
- coldStream
- Kotlin
- app-distribution
- ServerDrivenUI
- flowon
- Advanced LCA
- conflate
- 릴리즈 키해시
- ShapeableImageView
- 안드로이드
- SDUI
- KAKAO
- coroutine
- coroutinescope
- monotone stack
- Today
- Total
루피도 코딩한다
[Coroutine Basics 7] Suspending function 본문
Suspend함수는 Suspend함수 내부에서 호출 가능하다. delay() 또한 suspend 함수의 일종이다.
아래 코드에는 suspend라는 키워드가 명시적으로 사용되고 있지 않은데 어떻게 delay()를 활용할 수 있을까?
fun main(): Unit = runBlocking {
launch {
delay(100L)
}
}
그 이유는 runBlocking에 있다. 지난 포스트에서도 한번 봤었지만 다시 runBlocking 함수의 내부 구조를 살펴보도록 하자!
Builders.kt
내부
public actual fun <T> runBlocking(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T)
: T { ...}
여기서 block 부분을 보면 함수 타입에 suspend
가 붙어있다. 이러한 것을 Suspending lambda 라고 한다.
그리고 이렇게 suspend 키워드가 붙어 함수가 중지 되었다가 정지 될 수 있음을 명시해 주는데, 이를 suspension point
라고 한다.
그래서 suspend
를 어떻게 야무지게 사용할 수 있을까?
먼저 아래 코드를 봐보자. Main() 함수에서는 result1과 result2의 type이 Defereed 로 추론된다. 따라서 실제로 apiCall의 결과만 궁금한 main함수에서, 어떤 비동기처리 라이브러리에 의존했는지에 대한 정보까지 알아야 하는 셈이 된다.
적절한 비유일지는 모르겠지만..! Android의 viewmodel에서 coroutine을 활용하고자 할 때, 그 사실을 UI(Activity, Fragment)에서 알게하는 느낌일수 있을듯하다. 아래와 같은 구조로 바꿔서 api를 호출하는 곳에서는 어떤 비동기 처리를 사용할지 모르게 만들어 의존성을 낮출 수 있다.
이를 suspend
키워드를 활용해 비교적 라이브러리에 대한 의존성이 낮은 코드로 변경할 수 있다.
fun main(): Unit = runBlocking {
val result1 = async {
apiCall1()
}
val result2 = async {
apiCall2(result1.await())
}
printWithThread(result2.await())
}
fun apiCall1(): Int {
Thread.sleep(1_000L)
return 100
}
fun apiCall2(num: Int): Int {
Thread.sleep(1_000L)
return num * 2
}
fun main(): Unit = runBlocking {
val result1 : Int = apiCall1() // 순수한 코틀린 type에만 의존할 수 있게 된다(<-> Deferred 객체)
val result2 = apiCall2(result1)
printWithThread(result2)
}
suspend fun apiCall1(): Int {
return CoroutineScope(Dispatchers.Default).async {
Thread.sleep(1_000L)
100
}.await()
}
// cf. CompletableFuture는 Java에서 제공하는 비동기 프로그래밍 지원 클래스이다.
suspend fun apiCall2(num: Int): Int {
return CompletableFuture.supplyAsync {
Thread.sleep(1_000L)
num * 2
}.await()
}
suspend 키워드를 활용해 apiCall1()
과 apiCall2()
내부에서 어떤 비동기 라이브러리 구현체를 사용했는지에 대한 정보를 몰라도 main() 함수를 작성할 수 있는 구조가 되었다.
참고 by GPT
apiCall1의 경우 coroutine 라이브러리를 활용한 함수고, apiCall2의 경우 CompletableFuture를 활용한 함수인데 두 함수가 공통적으로 await라는 함수를 활용할수 있는 이유는 무엇일까?
Kotlin은 다양한 비동기 라이브러리와의 통합을 지원하는 어댑터를 활발하게 제공한다. 예를 들어, Deferred
는 Kotlin의 async
와 함께 사용되는 코루틴 라이브러리에서 제공하는 인터페이스이며, CompletableFuture
는 Java에서 제공하는 비동기 라이브러리이지만, await
함수를 통해 이러한 라이브러리 간의 상호 운용성을 쉽게 달성할 수 있다고 한다
[suspend function 1] coroutineScope
- <-> launch와 async와 비교
- 공통점 : 새로운 코루틴을 만듦
- 차이점 : 주어진 함수 블록이 바로 실행됨
- 새로 생긴 코루틴과 자식 코루틴이 모두 완료된 이후 반환된다
[suspend function 2] withContext
- 새로운 코루틴 만들기 + 코드 블록이 즉시 호출 && 코루틴이 완전히 종료되어야 반환
- coroutineScope와 유사하나 context의 변화를 줄 수 있음
withContext(NonCancellable)
을 이용해 취소 불가능한 블록을 만들 수 있다
[suspend function 3] withTimeout
fun main() = runBlocking {
withTimeout(1_000L){
delay(1_500L)
30
}
}
// TimeoutCancellationException 발생
// Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
- Try- catch로 exception을 핸들링 하거나, 아래의
withTimeoutOrNull
사용하는 방법이 있음
[suspend function 4] withTimeoutOrNull
timeout 발생 시 null 반환 -> elvis 연산자로 (exception 보다 쉽게) 핸들링 가능
fun main() = runBlocking { val result = withTimeoutOrNull(500L) { doCount() true } ?: false ✅ println(result) }
(부록) Kotlin Expression Body
Kotlin에서 함수나 프로퍼티 등을 정의할 때 중괄호 {}
블록 대신에 등호 =
뒤에 짧은 표현식을 사용하여 정의하는 문법을 "expression body"라고 한다. 이를 통해 간결하고 명료한 코드를 작성할 수 있습니다. Expression body는 간단한 함수나 프로퍼티 정의에서 특히 유용하다.
// 일반적인 코드
fun add(a: Int, b: Int): Int {
return a + b
}
// expression body 코드
fun add(a: Int, b: Int): Int = a + b
// return type 생략 코드 (type 추론 가능)
fun add(a: Int, b: Int) = a + b
이렇게 나타내는 표현 방식을 익숙하게 쓰고 있었지만 'Expression Body'라는 표현이 낯설게 느껴져 한번 정리해보았다
// 아래 두 코드가 동일
suspend fun doSomething() = coroutineScope { ... }
suspend fun doSomething() {
coroutineScope{...}
}
'Coroutine' 카테고리의 다른 글
[Flow Basics 2] Flow Intermediate and Terminal Operators (0) | 2024.01.26 |
---|---|
[Flow Basics 1] Flow 기초(Flow Builder, Cold/Hot Stream) (0) | 2024.01.26 |
[Coroutine Basics 6] Structured Concurrency, CoroutineScope, CoroutineContext (0) | 2024.01.22 |
[Coroutine Basics 5] 코루틴의 예외 처리와 Job의 상태 변화 (1) | 2024.01.21 |
[Coroutine Basics 4] Coroutine Cacelation (0) | 2024.01.19 |