信号量如何使异步循环保持有序



我已经设置了这个脚本来在后台循环处理一堆数据,并且我已经成功地设置了一个信号量来保持所有内容(将填充表的数组(的有序,但我无法确切地理解信号量是如何或为什么保持数组的有序的。进入dispatchGroup,循环停止并等待,直到下载图像,一旦获得图像,dispatchSemaphore被设置为1dispatchGroup立即退出,信号量被设置回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()
}
}

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

因此,在你开始下载后,你会立即等待你的信号量。这将被阻塞,直到在getData方法的回调闭包中发出信号。只有这样,循环才能继续到下一个文档并下载它。这样,你就可以下载一个又一个文件,并在下载过程中阻止当前线程。

在这里,使用串行队列不是一个选项,因为这只会导致下载以串行方式开始,但不能影响它们完成的顺序。

这是一个相当低效的。如果您同时向网络层发出多个请求(想想并行下载和HTTP管道(,那么网络层可能会运行得更快。此外,你在"浪费"一个线程,这个线程可以同时做一些不同的工作。如果同时还有更多的工作要做,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
}
}
}

DispatchGroup在这里什么都没做。您具有DispatchSemaphor授予的互斥,并且排序仅由snapshot.documents的迭代顺序提供

最新更新