メインコンテンツにスキップ

⏸️ suspend関数

📖 suspendとは?

suspend関数は、コルーチンを一時停止できる特別な関数です。スレッドをブロックせずに待機できるため、効率的です!

💡 基本概念

suspendキーワード

// suspend関数は他のsuspend関数を呼び出し可能
suspend fun doSomething() {
delay(1000) // suspend関数
println("完了!")
}

fun main() = runBlocking {
doSomething() // コルーチン内で呼び出し
}

通常の関数 vs suspend関数

// ❌ 通常の関数ではsuspend関数の呼び出しは不可
fun normalFunction() {
// delay(1000) // コンパイルエラー!
}

// ✅ suspend関数では可能
suspend fun suspendFunction() {
delay(1000) // OK!
}

🎯 実践例

ネットワークリクエスト

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

suspend fun fetchUser(userId: String): User {
delay(1000) // ネットワーク遅延のシミュレーション
return User(userId, "홍길동")
}

fun main() = runBlocking {
println("要請 시작")
val user = fetchUser("user123")
println("받은 유저: ${user.name}")
}

順次実行

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
}

並列実行

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

ディスパッチャーの切り替え

suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
// IOスレッドで実行
delay(1000)
"データ"
}
}

fun main() = runBlocking {
println("メインスレッド: ${Thread.currentThread().name}")

val data = loadData()

println("再びメイン: ${Thread.currentThread().name}")
println("データ: $data")
}

複数のコンテキスト

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

// CPU集約的な作業
val processed = withContext(Dispatchers.Default) {
heavyComputation(data)
}

// メインスレッド(UI更新など)
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")
}

🎨 高度なパターン

条件付き待機

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

suspend fun fetchFromNetwork(): String {
delay(1000)
return "新しいデータ"
}

fun main() = runBlocking {
// キャッシュあり - 即座に返却
val cached = fetchDataIfNeeded("キャッシュデータ")
println(cached)

// キャッシュなし - ネットワークリクエスト
val fresh = fetchDataIfNeeded(null)
println(fresh)
}

チェーン

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

エラーハンドリング

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 ?: "失敗"}")
}

🔥 実用的な関数

タイムアウト処理

suspend fun fetchWithTimeout(): String? {
return try {
withTimeout(2000) {
delay(3000) // 時間がかかりすぎる
"データ"
}
} catch (e: TimeoutCancellationException) {
println("タイムアウト!")
null
}
}

fun main() = runBlocking {
val result = fetchWithTimeout()
println("結果: ${result ?: "タイムアウト"}")
}

リトライロジック

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() // 最後の試行
}

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

キャッシュパターン

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()

// 最初の呼び出し - ネットワーク
println(repo.getData())

// 2回目の呼び出し - キャッシュ
println(repo.getData())

// 強制リフレッシュ
println(repo.getData(forceRefresh = true))
}

🛡️ キャンセル可能な関数

キャンセル確認

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() // キャンセルされると例外発生

println("重い作業$i")
Thread.sleep(500) // CPU作業のシミュレーション
}
}

fun main() = runBlocking {
val job = launch {
try {
heavyWork()
} catch (e: CancellationException) {
println("作業がキャンセルされました")
}
}

delay(2000)
job.cancel()
}

🤔 よくある質問

Q1. suspend関数はスレッドを生成しますか?

A: いいえ!suspendはスレッドをブロックせずに一時停止するだけです。

suspend fun example() {
println("開始: ${Thread.currentThread().name}")
delay(1000)
println("終了: ${Thread.currentThread().name}")
}
// 同じスレッドで実行されることがあります!

Q2. すべての関数をsuspendにすると?

A: 不要です!非同期作業がある時だけ使用してください。

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

// ✅ 必要
suspend fun fetchData() = withContext(Dispatchers.IO) {
// ネットワークリクエスト
}

Q3. suspend関数から通常の関数の呼び出しは?

A: もちろん可能です!

fun normalFunction() = println("通常の関数")

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

🎬 おわりに

suspend関数で効率的な非同期コードを!

重要なまとめ:
✅ suspendで一時停止可能
✅ スレッドブロックなしで待機
✅ withContextでスレッド切り替え
✅ 順次/並列実行の制御
✅ キャンセル可能な作業

次のステップ: Flowで非同期データストリームを学びましょう!