はじめに
〜スレッドを安全に切り替える suspend 関数〜
概要(Overview)
withContext は、コルーチンのコンテキスト(特にスレッド Dispatcher)を一時的に切り替えるための関数 です。
主に「重い処理をバックグラウンドに」「結果をUIスレッドに戻す」などの場面で使います。
一言で言うと
withContext= 一時的に別スレッドで処理し、結果を返す suspend 関数
たとえば:
val result = withContext(Dispatchers.IO) {
// I/O専用スレッドで実行
fetchDataFromNetwork()
}
// 結果をもとにUI更新(Mainスレッド側)
基本構文
suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
-
引数
-
context:実行するスレッド(例:Dispatchers.IO) -
block:実際に処理を行うsuspendラムダ
-
-
戻り値
-
blockの戻り値そのものを返す
-
コード例①:スレッドを切り替える
import kotlinx.coroutines.*
suspend fun fetchData(): String {
return withContext(Dispatchers.IO) {
println("Fetching on ${Thread.currentThread().name}")
delay(1000)
"Data loaded"
}
}
fun main() = runBlocking {
val data = fetchData()
println("Received on ${Thread.currentThread().name}: $data")
}
出力例:
Fetching on DefaultDispatcher-worker-1
Received on main: Data loaded
IOスレッドで処理 → Mainスレッドで結果を受け取る
コード例②:UIスレッドとの連携(Android実戦)
class UserViewModel : ViewModel() {
fun loadUser() {
viewModelScope.launch(Dispatchers.Main) {
val user = withContext(Dispatchers.IO) {
getUserFromDatabase()
}
updateUI(user)
}
}
}
ポイント:
-
withContext(Dispatchers.IO):DBからデータ取得(I/O処理) - 戻り値が完了したら自動で
Dispatchers.Mainに戻る - UIスレッドをブロックせず安全に更新できる
コード例③:ネストしたスレッド切り替え
runBlocking {
val result = withContext(Dispatchers.Default) {
val ioResult = withContext(Dispatchers.IO) {
"File content"
}
"Processed: $ioResult"
}
println(result)
}
Dispatcherの切り替えをネストしてもOK。
コルーチンがスレッドを自由に「行き来」できます。
コード例④:例外処理とキャンセル
withContext は suspend関数なので、通常のtry-catchで例外を処理できます。
try {
val result = withContext(Dispatchers.IO) {
if (Random.nextBoolean()) throw IOException("Network error")
"Success"
}
println(result)
} catch (e: IOException) {
println("Caught: ${e.message}")
}
ポイント:
-
withContext内の例外は呼び出し元に伝播 - 親コルーチンのキャンセルも自動伝播(構造化並行性)
コード例⑤:計算とI/Oを組み合わせる
suspend fun analyzeFile(): Int {
val text = withContext(Dispatchers.IO) {
File("data.txt").readText()
}
return withContext(Dispatchers.Default) {
text.split("\\s+".toRegex()).size
}
}
I/O と CPU計算 の両方を効率よく分離できる構造。
- I/O は
Dispatchers.IO - 計算は
Dispatchers.Default
withContext vs launch vs async
| 関数 | 目的 | 戻り値 | 並列性 | 用途 |
|---|---|---|---|---|
launch |
処理を「開始」する | Job |
あり(独立) | Fire-and-forget 処理 |
async |
並列に「結果を返す」 | Deferred<T> |
あり | 並列計算 |
withContext |
スレッドを「切り替える」 | T |
なし(順次) | 一時的なコンテキスト変更 |
withContext は「別スレッドで実行して結果を受け取る」直列的構造。
並列実行したいときは async を使う。
内部的な動作イメージ
withContext は 一時的にIOへ「ジャンプ」し、処理が終わったら元のスレッドに戻る。
性能最適化のポイント
-
withContextの呼び出しは軽量(数十ナノ秒〜数百ナノ秒程度) - スレッド切り替えコストはOS依存(不要な切り替えは避ける)
- 小さいタスクを大量に切り替えるより、処理をまとめて1回切り替える方が効率的
注意点
| 注意点 | 説明 |
|---|---|
| ブロッキングAPIをMainで使わない | UIフリーズの原因になる |
| 無駄な切り替えを避ける |
withContext(Dispatchers.IO) の入れ子多用は非効率 |
withContext は suspend関数内でのみ使用可能 |
通常関数から直接呼べない |
まとめ
| 概念 | 説明 |
|---|---|
withContext |
コルーチンのスレッドを安全に切り替える |
| 特徴 | suspend関数・結果を返す・構造化並行性対応 |
| よく使う組み合わせ |
Dispatchers.IO / Dispatchers.Default / Dispatchers.Main
|
| 利点 | UIフリーズ防止、非同期I/Oの簡潔化、明確な責務分離 |
| 注意 | 無駄なスレッド切り替えは避けること |