信号量如何保持异步循环的顺序?

How does the semaphore keep async loop in order?

我已将此脚本设置为在后台循环遍历一堆数据,并且已成功设置一个信号量以保持所有内容(将填充 table 的数组)按顺序排列但是我不能完全理解信号量如何或为什么使数组保持有序。输入 dispatchGroup,循环停止并等待图像下载,一旦图像被获取,dispatchSemaphore 设置为 1 并立即退出 dispatchGroup 并信号量设置回 0。信号量从 0 切换到 1 的速度如此之快,以至于我不明白它是如何保持数组有序的。

let dispatchQueue = DispatchQueue(label: "someTask")
let dispatchGroup = DispatchGroup()
let dispatchSemaphore = DispatchSemaphore(value: 0)

dispatchQueue.async {

    for doc in snapshot.documents {

        // create data object for array

        dispatchGroup.enter()

        // get image with asynchronous completion handler
        Storage.storage().reference(forURL: imageId).getData(maxSize: 1048576, completion: { (data, error) in

            defer {
                dispatchSemaphore.signal()
                dispatchGroup.leave()
            }

            if let imageData = data,
                error == nil {
                // add image to data object
                // append to array
            }

        })

        dispatchSemaphore.wait()

    }

    // do some extra stuff in background after loop is done

}

dispatchGroup.notify(queue: dispatchQueue) {

    DispatchQueue.main.async {
        self.tableView.reloadData()
    }

}

DispatchGroup 在这里并没有真正做任何事情。 DispatchSemaphor 允许互斥,排序仅由 snapshot.documents

的迭代顺序提供

解决方案在您的评论中 get image with asynchronous completion handler。如果没有信号量,所有图像下载将同时开始并竞相完成,因此下载速度最快的图像将首先添加到数组中。

因此,在您开始下载后,您会立即等待您的信号量。这将阻塞,直到它在 getData 方法的回调闭包中发出信号。只有这样循环才能继续下一个文件并下载它。通过这种方式,您可以一个接一个地下载文件,并在下载 运行ning 时阻塞当前线程。

这里不能使用串行队列,因为这只会导致下载开始串行,但您不能影响它们完成的顺序。

虽然这样效率很低。如果您同时给它多个请求,您的网络层可能会 运行 更快(想想并行下载和 HTTP 流水线)。此外,您是 'wasting' 一个可以同时做一些不同工作的线程。如果同时有更多工作要做,GCD 将产生另一个线程,这会浪费内存和其他资源。

更好的模式是跳过信号量,让下载 运行 并行并将图像直接存储在数组中的正确索引处。这当然意味着您必须事先准备一个适当大小的数组,并且您必须为丢失或失败的图像考虑一个占位符。可选项可以很好地解决问题:

var images: [UIImage?] = Array(repeating: nil, count: snapshot.documents.count)

for (index, doc) in snapshot.documents.enumerated() {

    // create data object for array

    dispatchGroup.enter()

    // get image with asynchronous completion handler
    Storage.storage().reference(forURL: imageId).getData(maxSize: 1048576) { data, error in

        defer {
            dispatchGroup.leave()
        }

        if let imageData = data,
            error == nil {
            // add image to data object
            images[index] = image
        }
    }
}