😸

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