Task / TaskGroup / async let
| 対応: | Swift 5.5(2021) |
|---|
Swiftの『Task』は非同期処理の単位です。独立したタスクを作成したり、『withTaskGroup』で動的な並列処理を管理したりできます。
構文
// 新しいタスクの作成
Task {
await 非同期処理()
}
// 優先度付きタスク
Task(priority: .high) {
await 重要な処理()
}
// デタッチドタスク(現在のコンテキストから独立)
Task.detached {
await バックグラウンド処理()
}
// タスクグループ(動的な並列処理)
await withTaskGroup(of: 型.self) { group in
group.addTask { await 処理1() }
group.addTask { await 処理2() }
for await result in group {
// 各タスクの結果を処理
}
}
構文一覧
| 構文 | 概要 |
|---|---|
| Task { } | 新しい非同期タスクを作成します。現在のアクターコンテキストを継承します。 |
| Task.detached { } | 現在のコンテキストから独立したタスクを作成します。 |
| Task.cancel() | タスクをキャンセルします。 |
| Task.isCancelled | タスクがキャンセルされているか確認します。 |
| withTaskGroup(of:) | 動的な数の並列タスクを管理するタスクグループを作成します。 |
| withThrowingTaskGroup(of:) | エラーを投げるタスクグループを作成します。 |
| group.addTask { } | タスクグループにタスクを追加します。 |
サンプルコード
sample_task_taskgroup.swift
import Foundation
// タスクのキャンセル
let task = Task {
for i in 1...10 {
try Task.checkCancellation() // キャンセルされていれば CancellationError を throw
try await Task.sleep(nanoseconds: 100_000_000)
print("処理 \(i) 完了")
}
}
Task {
try await Task.sleep(nanoseconds: 300_000_000)
task.cancel()
print("タスクをキャンセルしました")
}
// withTaskGroup: 動的な並列処理
func fetchAllUsers(ids: [Int]) async -> [String] {
let users = [1: "狡噛慎也", 2: "常守朱", 3: "宜野座伸元", 4: "征陸智己"]
return await withTaskGroup(of: String?.self) { group in
for id in ids {
group.addTask {
try? await Task.sleep(nanoseconds: 100_000_000)
return users[id]
}
}
var results: [String] = []
for await name in group {
if let name = name {
results.append(name)
}
}
return results
}
}
Task {
let names = await fetchAllUsers(ids: [1, 2, 3, 4, 5])
print("取得したユーザー: \(names.sorted())")
}
task_taskgroup2.swift — withThrowingTaskGroup でエラーを扱う
import Foundation
enum FetchError: Error {
case notFound(Int)
}
func fetchScore(id: Int) async throws -> Int {
try await Task.sleep(nanoseconds: 50_000_000)
let scores = [1: 85, 2: 92, 3: 78]
guard let score = scores[id] else {
throw FetchError.notFound(id)
}
return score
}
func totalScore(ids: [Int]) async -> Int {
do {
return try await withThrowingTaskGroup(of: Int.self) { group in
for id in ids {
group.addTask { try await fetchScore(id: id) }
}
var total = 0
for try await score in group {
total += score
}
return total
}
} catch FetchError.notFound(let id) {
print("ID \(id) が見つかりません")
return -1
} catch {
return -1
}
}
Task {
let result = await totalScore(ids: [1, 2, 3])
print("合計スコア: \(result)") // 255
let bad = await totalScore(ids: [1, 99])
print("エラー時: \(bad)") // -1
}
task_taskgroup3.swift — Task.detached でバックグラウンド処理
import Foundation
@MainActor
func updateUI(message: String) {
print("[MainActor] \(message)")
}
func heavyWork(label: String) async -> String {
// 重い計算をシミュレートします。
try? await Task.sleep(nanoseconds: 100_000_000)
return "\(label) 完了"
}
Task {
// detachedは現在のコンテキストを引き継ぎません。
let result = await Task.detached(priority: .background) {
await heavyWork(label: "バックグラウンド処理")
}.value
await updateUI(message: result)
}
概要
『Task』はトップレベルや同期コードから非同期処理を開始するために使います。タスクはキャンセル可能で、長時間処理の途中で定期的に『Task.checkCancellation()』や『Task.isCancelled』を確認することが推奨されます。
『withTaskGroup』は配列の各要素を並列処理するような動的な並列処理に使います。タスク数がコンパイル時に決まらない場合に適しています。Task.detached は親のアクターコンテキストを継承しないため、UI の更新には @MainActor を明示的に指定する必要があります。
Actor についてはActor / データ競合の防止を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。