0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】Kotlin Coroutines の Dispatcher の仕組みとスレッド切り替え

0
Last updated at Posted at 2025-10-15

はじめに

― 「どのスレッドで動くのか」を支配するコルーチンの心臓部 ―


1. Dispatcherとは?

CoroutineDispatcher は、
コルーチンをどのスレッド(Thread)で実行するかを決める仕組みです。

コルーチン自体は軽量スレッドのような存在ですが、
実際の実行は「Dispatcher」によってスレッドプール上で行われます。


コルーチンの流れ(概念図)

Dispatcher は “スケジューラ” のような存在。
どの CPU コア・スレッドを使うかを決定します。


2. Kotlin 標準の Dispatcher 一覧

Dispatcher 説明 主な用途
Dispatchers.Default CPUバウンド処理向けスレッドプール 計算・圧縮・暗号化など
Dispatchers.IO I/O待機処理向けスレッドプール ファイル・DB・ネット通信など
Dispatchers.Main メイン/UIスレッド AndroidやDesktopのUI更新
Dispatchers.Unconfined スレッド未固定(呼び出し元依存) 特殊用途・テスト用

3. Dispatchers の内部構造

Default

  • CPUコア数に比例したスレッド数(例:8コア → 8スレッド)
  • ForkJoinPoolベース(commonPool
async(Dispatchers.Default) { heavyCalculation() }

高負荷の並列処理を行う際の標準選択肢。
スレッド数 ≒ CPUコア数。


IO

  • Defaultより多いスレッド数を持つ(最大64)
  • ネットワークやファイルなど「待ち時間が多い処理」に最適
withContext(Dispatchers.IO) {
    val data = URL("https://example.com").readText()
}

待ちが発生するI/O処理を「並行化」するために最適化されています。


Main

  • Android / Compose Desktop でUIスレッドを操作
  • Androidでは Looper.getMainLooper() にバインド
withContext(Dispatchers.Main) {
    textView.text = "更新完了!"
}

メインスレッドは UI更新専用
重い処理は絶対にここで行わないこと!


Unconfined

  • 最初は呼び出し元スレッドで実行
  • 次の中断ポイント後は再開スレッドが変わることもある
launch(Dispatchers.Unconfined) {
    println("開始: ${Thread.currentThread().name}")
    delay(100)
    println("再開: ${Thread.currentThread().name}")
}

出力例:

開始: main
再開: DefaultDispatcher-worker-1

⚠️ 予測不能な動作になるため、実務ではほぼ非推奨です。
テスト・低レベル実験向け。


4. withContext() によるスレッド切り替え

withContext(dispatcher) を使うと、
同じスコープ内でもスレッドを安全に切り替えることができます。

suspend fun fetchData() {
    val json = withContext(Dispatchers.IO) {
        println("I/Oスレッド: ${Thread.currentThread().name}")
        URL("https://example.com").readText()
    }

    withContext(Dispatchers.Main) {
        println("UIスレッド: ${Thread.currentThread().name}")
        println("データ表示: $json")
    }
}

出力:

I/Oスレッド: DefaultDispatcher-worker-2
UIスレッド: main

I/Oで取得 → MainでUI更新
という典型的な構造です。


5. Dispatcher の切り替えは軽量

withContext()スレッドをブロックしません
単に「次の中断ポイントを別のDispatcherで再開する」だけです。

したがって、以下のように連続切り替えしてもパフォーマンス低下はほとんどありません。

withContext(Dispatchers.IO) { ... }
withContext(Dispatchers.Default) { ... }
withContext(Dispatchers.Main) { ... }

6. よくある誤用と落とし穴

誤用 問題点 正しい方法
runBlocking(Dispatchers.IO) メインスレッドをブロック launch(Dispatchers.IO) または withContext
GlobalScope.launch(Dispatchers.Main) ライフサイクル無視 viewModelScope / lifecycleScope
Dispatchers.Unconfined 予測不能な再開スレッド 明示的に Default / IO を使用
重い処理を Main で実行 ANR / フリーズの原因 withContext(Dispatchers.Default) に移動

7. カスタムDispatcherを作る

val singleDispatcher = newSingleThreadContext("MyThread")

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

newSingleThreadContext() は 1つの専用スレッドを作成します。
⚠️ リソースリーク防止のため、使用後は close() が必要です。


ExecutorベースのDispatcher

val executor = Executors.newFixedThreadPool(4)
val myDispatcher = executor.asCoroutineDispatcher()

runBlocking(myDispatcher) {
    repeat(4) {
        launch { println("${Thread.currentThread().name} 実行中") }
    }
}

これにより、業務アプリに最適化された独自スレッドプールを実現可能。


8. スレッド切り替えの図解

コルーチンは withContext() を通して
スレッドプール間を 安全かつ非同期に移動 します。


9. ベストプラクティスまとめ

シーン 推奨 Dispatcher 理由
CPU負荷(計算・圧縮) Dispatchers.Default コア数最適化
ネット通信・DB・ファイルI/O Dispatchers.IO 多スレッド許容
UI更新 Dispatchers.Main メインスレッド操作
テスト・内部制御 Dispatchers.Unconfined 特殊用途のみ
長時間専用スレッド newSingleThreadContext() 独立タスク向け

まとめ

要点 内容
Dispatcher とは コルーチンのスレッドスケジューラ
Default CPUバウンド(計算系)
IO I/Oバウンド(待機系)
Main UIスレッド(Androidなど)
Unconfined 非固定スレッド(実験用)
スレッド切り替え withContext() で安全に行う
最適化の鍵 処理の性質に合わせてDispatcherを選択

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?