Swift TaskGroup 限制并发数量

import Foundation


actor Semaphore {
    private var value: Int
    private var waiters: [CheckedContinuation<Void, Never>] = []

    init(value: Int = 0) {
        self.value = value
    }

    func wait() async {
        value -= 1
        if value >= 0 { return }
        await withCheckedContinuation {
            waiters.append($0)
        }
    }

    func signal(value: Int = 1) {
        assert(value >= 1)
        self.value += value
        for _ in 0..<value {
            if waiters.isEmpty { return }
            waiters.removeFirst().resume()
        }
    }
}

func performTask(_ index: Int) async -> Int64 {
    await semaphore.wait()
    print("[\(index)] Starting task")

    let sleepTime =  2_000_000_000 + Int64(Float.random(in: -500_000_000...500_000_000))
    print("[\(index)] sleep for \(sleepTime) ns")
    try? await Task.sleep(nanoseconds: UInt64(sleepTime))
    print("[\(index)] Finished task \(index)")
    await semaphore.signal()
    return sleepTime
}


let semaphore = Semaphore(value: 2)

let returnValue = try? await withThrowingTaskGroup(
    of: Int64.self
) { group -> [Int64] in
    for i in 0..<5 {

        group.addTask {
            await performTask(i)
        }
    }


    var results = [Int64]()
    for try await value in group {
        results.append(value)
    }
    return results
}

if let returnValue{
    print("All tasks finished with results:")
    for i in 0 ..< returnValue.count {
        print("[\(i)] \(returnValue[i])")
    }
}

输出为

[0] Starting task
[1] Starting task
[0] sleep for 1824977088 ns
[1] sleep for 1873617056 ns
[0] Finished task 0
[1] Finished task 1
[2] Starting task
[3] Starting task
[3] sleep for 2433335872 ns
[2] sleep for 1926212064 ns
[2] Finished task 2
[4] Starting task
[4] sleep for 2300204992 ns
[3] Finished task 3
[4] Finished task 4

All tasks finished with results:
[0] 1824977088
[1] 1873617056
[2] 1926212064
[3] 2433335872
[4] 2300204992

这里如果用 DispatchSemaphore 会有如下报错 (GCD 和 Task Async-Await 不能混用)

Instance method 'wait' is unavailable from asynchronous contexts; Await a Task handle instead; this is an error in Swift 6

参考资料 https://forums.swift.org/t/semaphore-alternatives-for-structured-concurrency/59353