😸
Swift Concurrency④:withTaskGroupとは
一言で言うと
たくさんの非同期処理をまとめて並列に実行し、結果を安全に集めるための仕組み
例えば、このようなコードがあります。
import Foundation
// 重い処理の例(3秒かかる)
func heavyTask(_ number: Int) async -> Int {
print("Start task \(number)")
try? await Task.sleep(nanoseconds: 3_000_000_000) // 3秒待つ
print("End task \(number)")
return number * number // 処理結果として平方を返す
}
func runTasks() async {
await withTaskGroup(of: Int.self) { group in
// 1〜3 のタスクを追加
for i in 1...3 {
group.addTask {
return await heavyTask(i)
}
}
// 結果を受け取る
for await result in group {
print("Got result:", result)
}
}
}
// 実行例(Playgroundなら Task { await runTasks() })
Task {
await runTasks()
}
まずこのコードで何が起こっているのかというと、
①runTasks()が呼ばれる
②withTaskGroupの中にあるgroup.addTaskが呼ばれ、子タスクの処理がスタートします。
1、2、3の子タスクの処理が同時に始まります。
👉 ここで大事なのは:
「ループで順番に追加しているけど、待たないで全部同時に走り出す」 ということ。
③for await result in group で、終わった順に結果が返ってくる。
どんな時にwithTaskGroupが使われるん?
- 例:複数の API を一気に叩いて結果を集める
- 例:複数の画像を一気にダウンロードする
👉 タスクの数が動的に決まるときに特に便利
await withTaskGroup(of: Data.self) { group in
for url in imageURLs {
group.addTask {
return try await downloadImage(from: url)
}
}
var results: [Data] = []
for await data in group {
results.append(data)
}
}
①「終わった順」に結果を処理したいとき
②APIのレスポンスが重い・軽いでバラつく場合
👉 for await result in group を使えば 早く終わったものから処理できる。
③失敗やキャンセルを安全に扱いたいとき
Structured Concurrency なので、
スコープを抜けると残りのタスクは自動キャンセル。
だから「処理を投げっぱなしでリークする」心配がない。
④タスク数が増える場合でも柔軟に対応したいとき
async let は「固定数」のタスクに便利(例:ユーザー情報と画像の2つ)。
withTaskGroup は「可変数(ループで増える)」のときに使う。
具体的なアプリの例
SNSアプリ
→ 投稿のリストを取得して、各投稿に紐づく画像を同時にロードする
ニュースアプリ
→ 複数のニュース API を同時に呼んで、記事をまとめて表示する
翻訳アプリ
→ 複数の文を同時に翻訳して、結果を順次表示する
まとめ
- 👉 withTaskGroup を使うとき
- タスクの数が多い or 動的に変わる
- 並列に実行して効率を上げたい
- 結果を「終わった順」に処理したい
- スコープを抜けたら自動で片付けてほしい
💡 イメージとしては:
async let = 「少人数の宿題を一気に配る」
withTaskGroup = 「クラス全員に宿題を配って、終わった人から順に提出してもらう」
Discussion