Suspend functionは、suspend修飾子を持つ関数で、スレッドをブロックせずに実行を一時停止し、後で同じコルーチン内で再開できます。JetBrains Kotlin Docs, 2025によると、suspend関数はコルーチンの基本的な構成要素であり、コールバックなしで非同期処理を提供します。各suspend関数はContinuationに基づくステートマシンにコンパイルされ、一時停止ポイントを効率的に管理できます。
重要ポイント
Suspend functionは、suspendキーワードで宣言された関数で、スレッドをブロックせずに1つ以上のポイントで実行を一時停止できます。別のsuspend関数内でのsuspend関数の各呼び出しは、潜在的な一時停止ポイントです。
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
Kotlinコンパイラはそのような関数をステートマシンに変換します。各一時停止ポイント(別のsuspend関数の呼び出し)は状態(label)になります。現在のスレッドは状態間で解放され、待機中の操作が完了すると、次の状態から実行が再開されます。
suspend関数はKotlin 1.3(2018年)でコルーチンとともに実験的機能として登場し、Kotlin 1.5(2021年)で安定化されました。それ以前は、Kotlin/Javaでの非同期処理はコールバック、RxJava、CompletableFutureを通じて実現されていました。suspend関数は、直線的な構文と自動スレッド管理を備えた代替手段を提供しました。
suspend関数の内部動作を理解することは、コルーチンを正しく扱うための鍵です。通常の関数とは異なり、各suspend関数はContinuationインターフェースを持つクラスにコンパイルされます。
Kotlinコンパイラは、各suspend関数のパラメータリストの最後にContinuation型のパラメータを追加します。Continuationには以下が含まれます:
他のsuspend関数への2つの呼び出しを持つsuspend関数があるとします:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
コンパイラはそれをラベル付きのステートマシンに変換します:
// Simplified generated code representation
fun process(cont: Continuation<Unit>): Any? {
val cont = cont as ProcessContinuation
when (cont.label) {
0 -> {
cont.label = 1
if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
1 -> {
cont.label = 2
val a = cont.result as TypeA
if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
2 -> {
println(cont.result)
Unit
}
}
}
重要な観察:関数がCOROUTINE_SUSPENDEDを返す場合、現在のスレッドは解放されます。非同期操作が完了すると、Continuation.resumeWithが呼び出され、ステートマシンは次のラベルから続行します。
suspend関数の宣言は、funの前のsuspendキーワードを除いて、通常の関数と変わりません。制限は1つだけです:suspend関数はコルーチンまたは別のsuspend関数からのみ呼び出せます。
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
この例では、delayもsuspend関数であり、スレッドをブロックせずに指定されたミリ秒間コルーチンを一時停止します。遅延後、実行が再開されます。
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlockingは通常の世界とコルーチンの間の橋渡しをします。ラムダ内では、任意のsuspend関数を呼び出すことができます。
Kotlinは関数型のsuspendバージョンをサポートしています — suspend () -> T および suspend (A) -> B。これにより、高階関数に非同期ラムダを渡すことができます:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
withRetry関数はsuspendラムダを受け取り、エラー時にその実行を再試行します。これはリトライ付きネットワークリクエストの典型的なパターンです。
suspend関数と通常の関数の違いは、単に修飾子を追加するだけにとどまりません。主な違いを見てみましょう。
| 特性 | 通常の関数 | suspend関数 |
|---|---|---|
| 実行スレッド | 完了までスレッドをブロック | スレッドを解放して後で再開可能 |
| コンパイラパラメータ | 指定されたパラメータのみ | 末尾に暗黙のContinuation |
| 通常の関数からの呼び出し | 可 | 不可 |
| スタック | 物理スレッドスタック | ヒープ上のステートマシン + ポイント間の物理スタック |
| 戻り値 | 直接値 | 値またはCOROUTINE_SUSPENDED |
| パフォーマンス | 最小限のオーバーヘッド | ステートマシンあたり約数ナノ秒(Kotlin 1.9+) |
通常の関数にはContinuationがありません — 状態を保存する場所も、実行を再開する手段もありません。通常の関数からsuspend関数を呼び出す必要がある場合は、runBlocking(テスト用)またはCoroutineScope.launch(ライフサイクルを考慮した本番用)を使用してください。
Kotlinを使用したAndroidアプリでのsuspend関数の3つの実際のシナリオを見てみましょう。
Roomはsuspend関数を直接サポートしています — クエリは自動的にバックグラウンドスレッドで実行されます:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Roomは内部でDispatchers.IOを使用してクエリを実行し、結果はsuspend関数が呼び出されたディスパッチャで返されます。
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launchはコルーチンを作成し、その内部でgetProfile suspend関数が呼び出されます。結果を取得した後、UIはメインスレッドで更新されます。
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
3つのsuspend関数が順次実行されます。各ステップで、コルーチンはスレッドをブロックせずに一時停止できます。いずれかのステップが例外をスローした場合、残りのステップは実行されず、不正な注文状態から保護します。
経験豊富なKotlin開発者でも、suspend関数を設計する際に間違いを犯します。最も一般的なものを見てみましょう。
suspend関数はコードを自動的に非同期にするわけではありません。Thread.sleep()、InputStream.read()、その他のブロッキング呼び出しは依然としてスレッドをブロックします。ブロッキング操作をラップするにはwithContext(Dispatchers.IO)を使用してください。
関数が他のsuspend関数を呼び出さず、非同期操作も実行しない場合 — suspend修飾子は冗長です。ステートマシンのオーバーヘッドが追加され、呼び出しコンテキストが制限されます。関数が実際に一時停止する場合にのみsuspendにしてください。
コルーチンがキャンセルされると、suspend関数はCancellationExceptionをスローします。それを無造作にキャッチしないでください — 呼び出し元のコードがキャンセルを適切に完了する能力を奪うことになります。最終処理操作を実行する必要がある場合は、finallyブロックとNonCancellableを使用してください。
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
finallyブロックはキャンセル時も含めて常に実行され、NonCancellableはクリーンアップが中断されないことを保証します。
コルーチンを作成せずに、コールバックから直接suspend関数を呼び出すことはできません。コールバックスタイルをコルーチンに適応させるには、suspendCoroutineまたはsuspendCancellableCoroutineを使用してください。
よくある質問
はい、技術的にはsuspend関数は他のsuspend関数を呼び出さなくても構いません。コンパイラは単一の状態(label 0)を持つステートマシンを作成します。しかし、そのような関数に実用的な利点はありません — 通常の関数のように実行されますが、オーバーヘッドがあります。不必要にsuspendを使用しないでください。
Kotlinはkotlinx-coroutines-debugを提供しています — DebugProbesとコルーチントレーシングツールを備えたライブラリです。Arctic Fox以降のAndroid Studioでは、デバッガに組み込みのCoroutinesタブがあり、アクティブなコルーチン、その状態、一時停止ポイントを表示します。
各一時停止ポイントはステートマシンに新しい状態を作成します。ほとんどのアプリケーションでは、1つのポイントのオーバーヘッドは数ナノ秒です(Kotlin 1.9+)。ループ内に数万のポイントがある場合のみ、操作を結合するかsequence/flowの使用を検討すべきです。
Kotlinでは、suspendは関数型の修飾子であり、戻り値のマーカー(C#のasyncなど)ではありません。任意のsuspend関数は任意のパラメータと戻り型を持つことができ、その呼び出しは構文的に通常の関数呼び出しと変わりません — 呼び出し場所にawait演算子はありません。
適応にはsuspendCancellableCoroutineを使用します。内部で、continuation.resume()を呼び出すコールバックを登録し、コールバックが購読解除をサポートする場合はキャンセレーショントークンを返します。これは古いAndroid APIをラップする標準的なパターンです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。