Async/Await:非同期関数とawaitの本質

著者: IT Sectr 公開日: 2026-03-16 読了時間: 8 分

Async/Awaitは、同期的なスタイルで非同期コードを記述するためのキーワードのペアで、SwiftではiOS 13以降、Kotlinではコルーチンで利用できます。Apple Swift Documentation, 2026によると、Async/AwaitはコールバックチェーンとGCDを置き換え、非同期コードを線形で読みやすくします。asyncキーワードは関数を非同期としてマークし、awaitは結果を受け取るまでその実行を一時停止します。

主要ポイント

  • Async/Awaitはネストされたコールバックなしで非同期コードを書くための構文糖
  • asyncは関数を非同期かつ一時停止可能としてマークする
  • awaitは非同期操作が完了するまで関数の実行を一時停止する
  • SwiftのTaskはコンテキストを持つ新しい非同期作業単位を作成する
  • Structured Concurrencyはすべての子タスクが親より先に完了することを保証する

Async/Awaitと非同期プログラミングとは

Async/Awaitは、同期コードと同じくらい線形的に非同期コードを記述できる言語構造です。SwiftではiOS 13 / macOS 10.15でSwift Concurrencyフレームワークとともに登場しました。Kotlinでは、バージョン1.3以降、コルーチンライブラリ(kotlinx.coroutines)を通じてasync/awaitが利用可能です。

async/await以前は、非同期コードはコールバック、GCD、またはRxSwiftに基づいて構築されていました。ネストされたコールバックの連鎖はコールバック地獄を引き起こし、深いネストが可読性とデバッグを困難にしました。Async/Awaitは、非同期関数がawaitポイントで“一時停止”し、結果を受け取った後に再開できるようにすることでこの問題を解決します。

Google(2025年)のデータによると、Kotlinでasync/awaitを使用すると、コールバックアプローチと比較して非同期コードの行数が40–60%削減されます。Swiftでは、2025年に公開されたアプリケーションの間でSwift Concurrencyの採用が68%に達しました。iOS 16はUIKit、SwiftUI、FoundationにSwift Concurrencyのサポートを追加し、async/awaitをすべての新しいAppleプロジェクトの標準ツールにしました。

動作原理:一時停止可能な関数

async/awaitとスレッドの主な違いはブロッキングなしの一時停止です。関数がawaitに遭遇すると、一時停止して現在のスレッドを他のタスクに解放します。非同期操作が完了すると、関数は同じまたは別のスレッドで再開されます。これを協調的マルチタスクと呼びます。

Async/Awaitの仕組み:一時停止と再開

非同期関数はステートマシンにコンパイルされます。これは実行状態を管理する有限オートマトンです。awaitポイントで、コンパイラはコンテキスト(ローカル変数、戻りアドレス)を保存し、制御をランタイムに渡します。結果が準備できると、ランタイムがコンテキストを復元して実行を続行します。

Swiftでは、このステートマシンはSIL(Swift Intermediate Language)コンパイラレベルで実装されます。Kotlinでは、Continuation Passing Style(CPS)メカニズムが使用されます。各suspend関数は、結果が返される隠れたContinuationパラメータを受け取ります。どちらのアプローチも、メモリリークなしで安全な再開を保証します。

  • Swift — コンパイラが非同期関数をrunAsyncコルーチンフレーム付きでSILに変換する
  • Kotlin — 各suspend関数がContinuationを受け取り、実行をディスパッチャに委任する
  • 両言語 — awaitでスレッドを解放し、待機中はブロックしない
  • 例外 — 標準メカニズム(throws / try-catch)を通じて伝播される

Async/Await vs コールバックとGCD:アプローチの比較

コールバックアプローチでは、操作の完了後に呼び出されるクロージャを渡す必要があります。3つの逐次リクエストがある場合、各レベルでエラー処理を伴う3レベルのネストが発生します。Async/Awaitは同じ3つのリクエストを、単一のcatchブロックを持つ3行の逐次コードに変換します。

GCD(DispatchQueue)はシリアルキューとDispatchGroupを通じてコールバック地獄を解決しますが、依然として冗長です。単純なタスク(ユーザーの読み込み、次に友達、次にUI)には、キュー、グループ、notifyブロックの作成が必要です。Async/Awaitは単一の関数内で3行で同じタスクを実行します。

側面コールバックGCDAsync/Await
ネスト深い(破滅のピラミッド)中程度(グループ+通知)線形(フラット)
エラー処理コールバックごと手動統一(try/catch)
キャンセル手動制限ありTask.cancel()
スレッドセーフ手動シリアルキュー経由MainActor経由

Apple WWDC 2024によると、Swift Concurrency async/awaitは新しいコードに推奨されるアプローチです。GCDはCライブラリとの統合や特定の低レベルスレッド処理シナリオのために残っています。

Swift Concurrency:Task、TaskGroup、Actor

TaskはSwiftにおける非同期作業の基本単位です。Taskは既存のアクターのコンテキスト内または任意のスレッド上で作成されます。Task内では、awaitを通じて非同期関数を呼び出すことができます。TaskGroupは複数の子タスクを並行して起動し、その結果を収集できます。これはDispatchGroupの強化版です。

Actorは、データ競合から状態を保護するスレッドセーフな参照型です。コンパイラは、アクターのプロパティへのアクセスが非同期呼び出しまたはアクターの分離内でのみ可能であることを保証します。Actorは、手動ロックなしで共有可変状態を保護するためのシリアルDispatchQueueを置き換えます。

Structured Concurrency

Structured Concurrencyの原則は次の通りです。すべての非同期タスクには親があり、親はすべての子が完了するまで完了しません。タスクAがTask { await B() }を起動すると、AはBを待ちます。これによりタスクリークが防止され、予測可能なライフサイクルが保証されます。

KotlinのコルーチンにおけるAsync/Await

Kotlinでは、async/awaitはコルーチンを通じて実装されます。コルーチンは言語の軽量スレッドです。関数はsuspendキーワード(Swiftのasyncに類似)でマークされます。起動にはlaunch(ファイアアンドフォーゲット)またはasync(結果あり)が使用されます。結果を取得するにはawaitを呼び出します。

Dispatcherは、コルーチンが実行されるスレッドプールを決定します。UIにはDispatchers.Main、ネットワーク/ディスクにはDispatchers.IO、CPU負荷の高いタスクにはDispatchers.Defaultです。Structured ConcurrencyはCoroutineScopeを通じて保証されます。スコープがキャンセルされると、すべての子コルーチンがキャンセルされます。

JetBrains(2025年)によると、Google PlayのAndroidアプリの97%がkotlinx.coroutinesを使用しており、その82%がネットワークリクエストにasync/awaitを採用しています。コルーチンはAndroidの非同期プログラミングのデファクトスタンダードになりました。Kotlin Multiplatformもコルーチンをサポートしており、Android、iOS、サーバーサイドで共有の非同期コードを使用できます。

SwiftとKotlinでのAsync/Awaitのコード例

各言語の2つの例を見てみましょう。逐次データ読み込みと、TaskGroupまたはasync/awaitを使用した並行リクエストです。

Swift:逐次的な非同期呼び出し

awaitはユーザーデータを受け取るまで、次に友達データを受け取るまで実行を一時停止します。ネストされたクロージャはありません。

swift
func loadProfile() async throws -> Profile {
    let user = try await api.fetchUser()
    let friends = try await api.fetchFriends(for: user.id)
    return Profile(user: user, friends: friends)
}

Swift:TaskGroupを使用した並行リクエスト

TaskGroupはタスクを並行して起動し、結果を配列に収集します。結果の順序は起動の順序と一致しない場合があります。

swift
func loadParallel() async throws -> [String] {
    await withThrowingTaskGroup(of: String.self) { group in
        group.addTask { try await api.fetchName() }
        group.addTask { try await api.fetchEmail() }
        group.addTask { try await api.fetchAvatar() }
        var results = [String]()
        for try await value in group { results.append(value) }
        return results
    }
}

Kotlin:async/awaitを使用した逐次呼び出し

suspend関数は他のsuspend関数を呼び出すことができます。Swiftに類似:逐次呼び出し。

kotlin
suspend fun loadProfile(): Profile {
    val user = api.fetchUser()
    val friends = api.fetchFriends(user.id)
    return Profile(user, friends)
}

Kotlin:async/awaitを使用した並行リクエスト

Kotlinでは、各リクエストにasyncを使用したcoroutineScopeを通じて並行性が実現されます。

kotlin
suspend fun loadParallel(): List<String> = coroutineScope {
    val name = async { api.fetchName() }
    val email = async { api.fetchEmail() }
    val avatar = async { api.fetchAvatar() }
    listOf(name.await(), email.await(), avatar.await())
}

Async/Awaitのよくある間違いと制限

Task { }の忘れ — 非同期関数はTaskでラップせずに同期コンテキストから呼び出すことはできません。Swiftでは、これはエラーとしてコンパイルされます。Kotlinでは、lifecycleScopeまたはviewModelScopeを通じて起動する必要があります。通常の関数からsuspend関数を呼び出そうとするとコンパイルされません。

不必要なasyncの過剰使用

すべての関数を非同期にする必要はありません。I/OのないCPU負荷の高いタスクはasyncの恩恵を受けません。.userInitiated QoSのDispatchQueueで実行する方が適しています。Async/AwaitはI/Oバウンドの操作(ネットワーク、ディスク、待機)に最適化されています。

.resultによるメインスレッドのブロッキング

メインスレッドで.resultを使用する(非同期関数の結果を同期的に待機する)とブロッキングが発生します。Swiftでは、Task.synchronousWaitはAppleによって推奨されていません。Kotlinでは、メインスレッドでのrunBlockingはアンチパターンです。lifecycleScopeを使用してください。

キャンセル時のTaskリーク

Taskがクラスのプロパティに保存されていない場合、コンテキストを離れるとキャンセルされます。Swiftでは、Taskはスコープ化されており、そのライフサイクルは作成元のコンテキストに結びついています。長時間実行される操作が必要な場合は、Taskをプロパティに保存してください。

よくある質問

async/awaitとDispatchQueueの違いは何ですか?

Async/Awaitはキューを作成する必要のない言語構造です。DispatchQueueはスレッドを管理するためのシステムAPIです。Async/awaitは言語レベルでステートマシンにコンパイルされ、GCDはOSレベルでシステムコールにコンパイルされます。

awaitはスレッドをブロックしますか?

いいえ — awaitは関数を一時停止しますが、他のタスクのためにスレッドを解放します。操作が完了すると、関数はプール内の利用可能な任意のスレッドで再開されます。

非同期タスクはキャンセルできますか?

はい — SwiftではTask.cancel()、KotlinではJob.cancel()を使用します。キャンセルはStructured Concurrencyに従ってすべての子タスクに伝播されます。コードはTask.isCancelledまたはensureActive()を通じてキャンセルフラグを確認します。

SwiftのMainActorとは何ですか?

MainActorは、そのプロパティとメソッドが常にメインスレッドで実行されるアクターです。関数を@MainActorでマークすることで、UIの更新が正しいスレッドで行われることを保証し、DispatchQueue.main.asyncを置き換えます。

async/awaitがある言語は?

Async/AwaitはSwift(iOS 13+)、Kotlin(kotlinx.coroutines経由)、Dart(Flutter)、JavaScript/TypeScriptPythonC#RustGo(構文糖を持つgoroutines経由)で利用可能です。

まとめ

  • Async/Awaitはコールバック地獄なしで線形的な非同期コードを書くための言語構造
  • Swift ConcurrencyはTask、TaskGroup、Actor、Structured Concurrencyを提供する
  • Kotlinコルーチンはsuspend関数、Dispatcher、コルーチンスコープを使用する
  • ブロッキングなしの一時停止がasync/awaitとスレッド・キューの主な違い
  • Structured Concurrencyはすべての子タスクが親より先に完了することを保証する
  • Async/Await vs GCD — 前者は新しいコードに推奨、後者はレガシーと低レベル用
  • SwiftのMainActorとKotlinのDispatchers.MainはUIスレッドをバックグラウンド操作から保護する

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください