🤹
【Swift】TaskGroupで並列に処理するタスクの数を制限したい
はじめに
TaskGroupを使って、複数のタスクを並列に処理することができます。
以下の基本的な例では、100個のタスクを並列に処理しています。
TaskGroupを使った基本的な実装
Task {
let numbers = try await echoNumbers(Array(0..<100))
print(numbers) // 結果: [0, 1, ... 98, 99]
}
func echoNumbers(_ numbers: [Int]) async throws -> [Int] {
try await withThrowingTaskGroup(of: Int.self) { group in
for num in numbers {
group.addTask { try await echoNumber(num) }
}
var result: [Int] = []
for try await num in group {
print(num)
result.append(num)
}
return result.sorted()
}
}
func echoNumber(_ number: Int) async throws -> Int {
try await Task.sleep(for: .milliseconds(Int.random(in: 500...1000)))
return number
}
本記事では、TaskGroupで並列に処理するタスクの数を制限する方法を解説します。
例えば、並列に処理するタスクを20個に制限したいとします。
呼び出し元で値を20個ずつ渡して5回分のechoNumbers
を実行するのは、効率が良くありません。
ひとつタスクが終わったらすぐに新たなタスクが追加され、常に20個のタスクが処理されていることが理想です。
実装
以下のように実装することで、並列に処理するタスクの数を制限することができます。
まず、limitTaskCount
の分だけタスクを追加します。
そしてfor await
で値を取り出すたびに、タスクが無くなるまでTaskGroupにタスクを追加していきます。
func echoNumbers(_ numbers: [Int]) async throws -> [Int] {
try await withThrowingTaskGroup(of: Int.self) { group in
let limitTaskCount = min(20, numbers.count)
for index in 0..<limitTaskCount {
group.addTask { try await echoNumber(numbers[index]) }
}
var result: [Int] = []
var nextIndex = limitTaskCount
for try await num in group {
if nextIndex < numbers.count {
let index = nextIndex
group.addTask { try await echoNumber(numbers[index]) }
nextIndex += 1
}
print(num)
result.append(num)
}
return result.sorted()
}
}
通常のfor文では、反復処理中にイテレーション対象を変更することはできません。
しかし、TaskGroupはfor await
構文を使っていることから分かる通りAsyncSequenceです。
したがって、ループでTaskGroupから値を取り出しながらそのスコープ内でさらにタスクを追加するというコードを書くことができます。
Discussion