Skip to main content

⏸️ suspend Functions

πŸ“– What is suspend?​

suspend functions are special functions that can suspend a coroutine. They can wait without blocking threads, making them efficient!

πŸ’‘ Basic Concepts​

suspend Keyword​

// suspend functions can call other suspend functions
suspend fun doSomething() {
delay(1000) // suspend function
println("μ™„λ£Œ!")
}

fun main() = runBlocking {
doSomething() // Call within a coroutine
}

Regular Functions vs suspend Functions​

// ❌ Cannot call suspend functions from regular functions
fun normalFunction() {
// delay(1000) // Compile error!
}

// βœ… Possible in suspend functions
suspend fun suspendFunction() {
delay(1000) // OK!
}

🎯 Practical Examples​

Network Request​

data class User(val id: String, val name: String)

suspend fun fetchUser(userId: String): User {
delay(1000) // Simulate network delay
return User(userId, "홍길동")
}

fun main() = runBlocking {
println("μš”μ²­ μ‹œμž‘")
val user = fetchUser("user123")
println("받은 μœ μ €: ${user.name}")
}

Sequential Execution​

suspend fun prepareFood(): String {
delay(1000)
return "μŒμ‹"
}

suspend fun setTable(): String {
delay(500)
return "ν…Œμ΄λΈ”"
}

suspend fun prepareMeal() {
println("μ€€λΉ„ μ‹œμž‘")

val food = prepareFood()
println("$food μ€€λΉ„ μ™„λ£Œ")

val table = setTable()
println("$table μ€€λΉ„ μ™„λ£Œ")

println("λͺ¨λ“  μ€€λΉ„ 끝!")
}

fun main() = runBlocking {
val time = measureTimeMillis {
prepareMeal()
}
println("μ†Œμš” μ‹œκ°„: ${time}ms") // ~1500ms
}

Parallel Execution​

suspend fun prepareMealParallel() = coroutineScope {
println("μ€€λΉ„ μ‹œμž‘")

val foodDeferred = async { prepareFood() }
val tableDeferred = async { setTable() }

val food = foodDeferred.await()
val table = tableDeferred.await()

println("$food, $table μ€€λΉ„ μ™„λ£Œ")
}

fun main() = runBlocking {
val time = measureTimeMillis {
prepareMealParallel()
}
println("μ†Œμš” μ‹œκ°„: ${time}ms") // ~1000ms (병렬!)
}

πŸ”§ withContext​

Dispatcher Switching​

suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
// Run on IO thread
delay(1000)
"데이터"
}
}

fun main() = runBlocking {
println("메인 μ“°λ ˆλ“œ: ${Thread.currentThread().name}")

val data = loadData()

println("λ‹€μ‹œ 메인: ${Thread.currentThread().name}")
println("데이터: $data")
}

Multiple Contexts​

suspend fun processData() {
// IO operation
val data = withContext(Dispatchers.IO) {
readFromFile()
}

// CPU-intensive operation
val processed = withContext(Dispatchers.Default) {
heavyComputation(data)
}

// Main thread (UI update, etc.)
withContext(Dispatchers.Main) {
updateUI(processed)
}
}

suspend fun readFromFile(): String {
delay(500)
return "파일 λ‚΄μš©"
}

suspend fun heavyComputation(data: String): String {
delay(1000)
return data.uppercase()
}

suspend fun updateUI(data: String) {
println("UI μ—…λ°μ΄νŠΈ: $data")
}

🎨 Advanced Patterns​

Conditional Waiting​

suspend fun fetchDataIfNeeded(cache: String?): String {
return cache ?: fetchFromNetwork()
}

suspend fun fetchFromNetwork(): String {
delay(1000)
return "μƒˆ 데이터"
}

fun main() = runBlocking {
// With cache - immediate return
val cached = fetchDataIfNeeded("μΊμ‹œ 데이터")
println(cached)

// No cache - network request
val fresh = fetchDataIfNeeded(null)
println(fresh)
}

Chaining​

suspend fun step1(): Int {
delay(500)
return 10
}

suspend fun step2(input: Int): Int {
delay(500)
return input * 2
}

suspend fun step3(input: Int): Int {
delay(500)
return input + 5
}

suspend fun processChain(): Int {
val a = step1()
val b = step2(a)
val c = step3(b)
return c
}

fun main() = runBlocking {
val result = processChain()
println("μ΅œμ’… κ²°κ³Ό: $result") // 25 (10 * 2 + 5)
}

Error Handling​

suspend fun riskyOperation(): String {
delay(1000)
throw Exception("λ„€νŠΈμ›Œν¬ 였λ₯˜!")
}

suspend fun safeOperation(): String? {
return try {
riskyOperation()
} catch (e: Exception) {
println("μ—λŸ¬ λ°œμƒ: ${e.message}")
null
}
}

fun main() = runBlocking {
val result = safeOperation()
println("κ²°κ³Ό: ${result ?: "μ‹€νŒ¨"}")
}

πŸ”₯ Practical Functions​

Timeout Handling​

suspend fun fetchWithTimeout(): String? {
return try {
withTimeout(2000) {
delay(3000) // Takes too long
"데이터"
}
} catch (e: TimeoutCancellationException) {
println("μ‹œκ°„ 초과!")
null
}
}

fun main() = runBlocking {
val result = fetchWithTimeout()
println("κ²°κ³Ό: ${result ?: "νƒ€μž„μ•„μ›ƒ"}")
}

Retry Logic​

suspend fun <T> retry(
times: Int = 3,
initialDelay: Long = 100,
factor: Double = 2.0,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
println("μ‹œλ„ ${attempt + 1} μ‹€νŒ¨: ${e.message}")
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong()
}
return block() // Last attempt
}

var attemptCount = 0

suspend fun unstableAPI(): String {
attemptCount++
if (attemptCount < 3) {
throw Exception("μΌμ‹œμ  였λ₯˜")
}
return "성곡!"
}

fun main() = runBlocking {
val result = retry(times = 5) {
unstableAPI()
}
println(result)
}

Cache Pattern​

class DataRepository {
private var cache: String? = null

suspend fun getData(forceRefresh: Boolean = false): String {
if (!forceRefresh && cache != null) {
println("μΊμ‹œμ—μ„œ λ°˜ν™˜")
return cache!!
}

println("λ„€νŠΈμ›Œν¬μ—μ„œ κ°€μ Έμ˜€λŠ” 쀑...")
delay(1000)
val data = "μƒˆ 데이터"
cache = data
return data
}
}

fun main() = runBlocking {
val repo = DataRepository()

// First call - network
println(repo.getData())

// Second call - cache
println(repo.getData())

// Force refresh
println(repo.getData(forceRefresh = true))
}

πŸ›‘οΈ Cancellable Functions​

Cancellation Check​

suspend fun longRunningTask() {
repeat(10) { i ->
if (!isActive) {
println("μ·¨μ†Œλ¨!")
return
}

println("μž‘μ—… $i")
delay(500)
}
}

fun main() = runBlocking {
val job = launch {
longRunningTask()
}

delay(2000)
println("μž‘μ—… μ·¨μ†Œ!")
job.cancelAndJoin()
}

ensureActive​

suspend fun heavyWork() {
repeat(10) { i ->
ensureActive() // Throws exception if cancelled

println("무거운 μž‘μ—… $i")
Thread.sleep(500) // Simulate CPU work
}
}

fun main() = runBlocking {
val job = launch {
try {
heavyWork()
} catch (e: CancellationException) {
println("μž‘μ—…μ΄ μ·¨μ†Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€")
}
}

delay(2000)
job.cancel()
}

πŸ€” Frequently Asked Questions​

Q1. Do suspend functions create threads?​

A: No! suspend only suspends without blocking threads.

suspend fun example() {
println("μ‹œμž‘: ${Thread.currentThread().name}")
delay(1000)
println("끝: ${Thread.currentThread().name}")
}
// Can run on the same thread!

Q2. What if I make all functions suspend?​

A: Unnecessary! Use it only when there are asynchronous operations.

// ❌ Unnecessary
suspend fun add(a: Int, b: Int) = a + b

// βœ… Necessary
suspend fun fetchData() = withContext(Dispatchers.IO) {
// Network request
}

Q3. Can suspend functions call regular functions?​

A: Of course!

fun normalFunction() = println("일반 ν•¨μˆ˜")

suspend fun suspendFunction() {
normalFunction() // OK!
delay(1000)
}

🎬 Conclusion​

Write efficient asynchronous code with suspend functions!

Key Points:
βœ… Suspend with suspend keyword
βœ… Wait without blocking threads
βœ… Switch threads with withContext
βœ… Control sequential/parallel execution
βœ… Cancellable operations

Next Step: Learn about asynchronous data streams in Flow!