Dispatchers — 主要概念、ディスパッチャの種類とその仕組み

著者: IT Sectr 公開日: 2026-06-22 読了時間: 9 分

DispatchersはKotlin CoroutinesにおけるCoroutineContextのコンポーネントで、コルーチンを実行するスレッドを決定します:Main(UIスレッド)、IO(ネットワークとディスク)、Default(CPU負荷の高いタスク)、Unconfined(現在のスレッド)。各ディスパッチャは特定の種類の作業に最適化された専門のスレッドプールを管理します。JetBrainsガイド、2024によると、適切なディスパッチャの選択はアプリケーションのパフォーマンスと安定性にとって重要です。

主要ポイント

  • Dispatchers — スレッド間のタスク分散を管理する定義済みのコルーチンディスパッチャ
  • Dispatchers.Main — Android UIスレッド、インターフェースの更新とViewの操作に使用
  • Dispatchers.IO — ネットワークリクエスト、ファイル読み書き、DB操作用のプール(最大64スレッド)
  • Dispatchers.Default — プロセッサコア数に基づくCPU負荷の高いタスク用のプール
  • Dispatchers.Unconfined — 現在のスレッドを継承、切り替えなし

Dispatchersとは?

DispatchersはCoroutineDispatcherインターフェースの実装であり、CoroutineContextの要素です。これらはコルーチンがどのスレッドまたはスレッドプールで実行されるかを決定します。launchまたはasyncを介してコルーチンを作成する際、ディスパッチャを最初のパラメータとして渡すことができます:launch(Dispatchers.IO) { ... }。ディスパッチャが指定されない場合、外部のCoroutineScopeから継承されます。

Kotlinは4つの組み込みディスパッチャを提供します:Main、IO、Default、Unconfined。各ディスパッチャは特定の種類の操作に最適化された独自のスレッドプールを使用します。適切なディスパッチャの選択がアプリケーションのパフォーマンスを決定します:誤った選択はUIの遅延、CPUコアのアイドル状態、または非効率的なスレッド使用につながります。

ディスパッチャスレッドプール最大スレッド数用途
Dispatchers.Main1つ(UI)1UI更新、LiveData、View
Dispatchers.IOIOプール64(limitedParallelism)ネットワーク、ファイル、DB
Dispatchers.DefaultCPUプールNコアソート、パース、計算
Dispatchers.Unconfined現在のスレッドなし中間操作、テスト

Dispatchers.Main: UIスレッド

Dispatchers.MainはAndroidのメインスレッドでコルーチンを実行するディスパッチャです。UI関連の操作(TextViewの更新、notifyDataSetChangedの呼び出し、LiveDataやStateFlowの操作)向けに設計されています。Androidでは、このディスパッチャはHandler(Looper.getMainLooper())を介して実装されます。

kotlin
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

コルーチンが既にMainディスパッチャ上にある場合、追加のwithContext(Dispatchers.Main)はオーバーヘッドを発生させません — ディスパッチャは現在のスレッドを確認し、切り替えをスキップします。withContextはディスパッチャ間の切り替えに推奨される方法です。

Dispatchers.IO: ネットワークとディスク操作

Dispatchers.IOはI/O操作用に最適化されたディスパッチャです:HTTPリクエスト(Ktor、OkHttp)、ファイルの読み書き、RoomやSQLDelightの操作。デフォルトで64スレッドのプールを使用し、負荷に応じてスケーリングします。新しいI/Oリクエストごとに、制限に達するまで追加のスレッドを作成できます。

並列性の制限

同時I/O操作の数を制御するには、limitedParallelism()を使用します。この関数は並列スレッド数に制限のある新しいディスパッチャを作成し、一括操作中のプール枯渇を防ぎます。

kotlin
val limitedIo = Dispatchers.IO.limitedParallelism(4)

// Load 100 files with limit of 4 concurrent operations
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

コルーチンが待機に時間を費やす(I/Oバウンド)すべての操作にはIOディスパッチャを使用してください。IOディスパッチャでのCPU負荷の高いタスクは非効率的です — I/O用のスレッドを占有し、システムのスループットを低下させます。

Dispatchers.Default: CPU負荷の高いタスク

Dispatchers.Defaultはプロセッサに負荷をかける計算操作用のディスパッチャです:ソート、フィルタリング、JSONパース(Moshi、Kotlinx Serialization)、画像処理、計算。プールサイズはプロセッサコア数と同じです(ただし2以上)。これにより、コンテキストスイッチングなしで最大のCPU使用率が保証されます。

kotlin
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
    return withContext(Dispatchers.Default) {
        input
            .parallelStream()
            .map { transform(it) }
            .toList()
    }
}

I/O操作にDispatchers.Defaultを使用しないでください — 計算タスクを処理できるCPUプールのスレッドをブロックします。IOとDefaultを分離することで、システムリソースの最適な活用が可能になります:IOスレッドはI/Oを待機し、CPUスレッドは常に計算でビジー状態です。

Dispatchers.Unconfined: 現在のスレッド

Dispatchers.Unconfinedはコルーチンをいかなるプールにもバインドしない特別なディスパッチャです。コルーチンはlaunch/asyncが呼び出されたスレッドで実行を開始し、一時停止後はresumeを呼び出したスレッドで再開します。この動作は、固定コンテキストを必要としない中間操作に適しています。

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("Before delay: ${Thread.currentThread().getName()}")
        delay(500L)
        println("After delay: ${Thread.currentThread().getName()}")
    }
}

プロダクションコードでは、Dispatchers.Unconfinedはほとんど使用されません。主な使用例:別のディスパッチャにデータを渡す前の軽量変換とテスト。プロダクションのワークロードでは、明示的なディスパッチャを使用してください — Unconfinedは実行スレッドがresumeの実装に依存するため予測できません。

ディスパッチャの選択方法

ディスパッチャの選択はタスクの種類によって異なります:UI操作→Main、I/Oバウンド→IO、CPUバウンド→Default、中間→スコープから継承。Androidでは、メインの作業が行われるディスパッチャでコルーチンを起動し、UIを更新する前にwithContextを介してMainに切り替えることをお勧めします。

  • MainディスパッチャでI/O操作を実行しないでください — UIをブロックします
  • IOディスパッチャでCPU負荷の高いタスクを実行しないでください — IOプールのスレッドを非効率的に消費します
  • 一括I/O操作時の並行性を制御するにはlimitedParallelismを使用してください
  • 新しいスコープを作成せずにwithContextを使用してディスパッチャを切り替えてください
  • コルーチンが特定のプールを必要としない場合はスコープからディスパッチャを継承してください

複雑なシナリオでは、+演算子を使用してディスパッチャを組み合わせます:Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler。これにより、指定されたディスパッチャ、エラーハンドリング、および分離されたJob階層を持つCoroutineContextが作成されます。

よくある質問

Dispatchers.IOとDispatchers.Defaultの違いは何ですか?

Dispatchers.IOはI/Oバウンド操作(I/O待機)に最大64スレッドのプールを使用しますが、Dispatchers.Defaultは計算タスクにCPUコア数に基づくプールを使用します。スレッドが不足すると、両方のプールが互いにスレッドを共有できます。

Kotlin Coroutinesで独自のディスパッチャを作成できますか?

はい、シングルスレッドの場合はnewSingleThreadContext()、固定プールの場合はnewFixedThreadPoolContext()を使用してください。プロダクションでは、既存のディスパッチャに基づいてlimitedParallelism()を使用してください — 新しいプールを作成するよりも効率的です。

バックグラウンドスレッドからDispatchers.Mainを呼び出すとどうなりますか?

Dispatchers.Mainが利用できない場合(JUnitテストやバックグラウンドサービスなど)、IllegalStateExceptionがスローされます。テストにはTestCoroutineDispatcher、バックグラウンドサービスにはDispatchers.IOまたはDefaultを使用してください。

並列I/O操作の数を制限するにはどうすればよいですか?

Dispatchers.IO.limitedParallelism(N)を使用します。Nは並列スレッドの最大数です。これにより、大量リクエスト時のプール枯渇を防ぎ、制御された並列性を提供します。

Dispatchers.Unconfinedはいつ使用しますか?

Dispatchers.Unconfinedは中間操作に適しています:別のディスパッチャに渡す前の軽量データ変換、テストシナリオ。プロダクションのAndroidコードでは、一時停止後の実行スレッドが未定義のため推奨されません。

まとめ

  • Dispatchers — コルーチンの実行スレッドを決定するCoroutineContextのコンポーネント
  • Dispatchers.Main — Android UI操作用、1つのメインスレッド
  • Dispatchers.IO — ネットワークとディスク操作用、最大64スレッドのプール
  • Dispatchers.Default — CPU負荷の高い計算用、コア数に基づくプール
  • Dispatchers.Unconfined — スレッドバインドなし、中間操作に使用
  • withContext — コルーチン内でディスパッチャを切り替える主要なメカニズム
  • 誤ったディスパッチャの選択はUIの遅延や非効率的なリソース使用につながる

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

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

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

こちらもお読みください