我在一个数组中有10个网址,当下载了4个网址时,我需要显示它们。我使用信号量和组来实现。但看起来我陷入僵局。不知道如何进行。请提供建议
在操场上模拟它:
PlaygroundPage.current.needsIndefiniteExecution = true
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 4)
var nums: [Int] = []
for i in 1...10 {
group.enter()
semaphore.wait()
queue.async(group: group) {
print("Downloading image \(i)")
// Simulate a network wait
Thread.sleep(forTimeInterval: 3)
nums.append(i)
print("Hola image \(i)")
if nums.count == 4 {
print("4 downloaded")
semaphore.signal()
group.leave()
}
}
if nums.count == 4 {
break
}
}
group.notify(queue: DispatchQueue.main) {
print(nums)
}
我在o / p控制台中得到了
> Downloading image 1
> Downloading image 2
> Downloading image 3
> Downloading image 4
信号量(41269,0x70000ade5000)malloc:对象0x1077d4750的错误:未分配要释放的指针
Semaphores(41269,0x70000ade5000)malloc:***在malloc_error_break中设置一个断点进行调试
我希望按顺序打印[1,2,3,4]
我知道我正在尝试异步访问共享资源,但不确定如何解决此问题。请指教
如果我想一次下载4,4,2个任务,使其在[symphore]中显示[1,2,3,4,5,6,7,8,9,10],该如何与信号量一起使用我的输出
答案 0 :(得分:0)
您的标题为“按url顺序下载图像”,但是您的代码段并未尝试这样做。似乎正在尝试使用信号量来将下载一次限制为四张图片,但这并不能保证它们会按顺序排列。
值得称赞的是,该代码段并未尝试依次下载它们,因为这会带来巨大的性能损失。很好的是,此代码片段将并发程度限制为合理的程度,从而避免了耗尽工作线程或导致某些后继请求超时。因此,使用信号量允许并发图像下载,但一次将其限制为四个的想法是一种很好的方法。我们只需要在结果排序时将结果排序。
但是在开始之前,让我们解决提供的代码段中的许多问题:
您每次迭代都调用group.enter()
和semaphore.wait()
(这是正确的),但是只有在group.leave()
为{时,才semaphore.signal()
和i
{1}}(不正确)。您希望每次迭代4
和leave
。
很显然,也不需要进行signal
调用。
因此,要解决此“一次执行四个”过程,可以简化此代码:
break
这将一次下载四张图像,并在完成后调用let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 4)
var nums: [Int] = []
for i in 1...10 {
group.enter()
semaphore.wait()
queue.async() { // NB: the `group` parameter is not needed
print("Downloading image \(i)")
// Simulate a network wait
Thread.sleep(forTimeInterval: 3)
nums.append(i)
print("Hola image \(i)")
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
print(nums)
}
关闭。
尽管以上内容修复了信号量和组逻辑,但上面的代码片段中还隐藏着另一个问题。它正在从多个后台线程更新group.notify
数组,但是nums
不是线程安全的。因此,您应该将这些更新同步到该阵列。一种简单的方法是将更新分派回主线程。 (任何串行队列都可以,但是主线程可以正常工作。)
此外,由于永远不要在主队列上调用Array
,因此建议您将整个wait
循环显式分派到后台线程:
for
现在正确的做法是“一次执行四个,然后在完成时通知我。”
好的,既然我们已经正确下载了所有图像,让我们弄清楚如何对结果进行排序。坦白说,如果我们认为我们有某种图像下载方法(例如这样)可以下载特定图像,那么我会更容易理解发生的事情:
DispatchQueue.global(qos: .utility).async {
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 4)
var nums: [Int] = []
for i in 1...10 {
group.enter()
semaphore.wait()
queue.async() {
print("Downloading image \(i)")
// Simulate a network wait
Thread.sleep(forTimeInterval: 3)
DispatchQueue.main.async {
nums.append(i)
print("Hola image \(i)")
}
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
print(nums)
}
}
然后执行以下程序:(a)一次下载图像,一次最多下载四张;和(b)将结果按顺序返回,看起来可能像这样:
func download(_ url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) { ... }
您会这样称呼它:
func downloadAllImages(_ urls: [URL], completion: @escaping ([UIImage]) -> Void) {
DispatchQueue.global(qos: .utility).async {
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 4)
var imageDictionary: [URL: UIImage] = [:]
// download the images
for url in urls {
group.enter()
semaphore.wait()
self.download(url) { result in
defer {
semaphore.signal()
group.leave()
}
switch result {
case .failure(let error):
print(error)
case .success(let image):
DispatchQueue.main.async {
imageDictionary[url] = image
}
}
}
}
// now sort the results
group.notify(queue: .main) {
completion(urls.compactMap { imageDictionary[$0] })
}
}
}
FWIW,“下载单个图像”例程可能类似于:
downloadAllImages(urls) { images in
self.images = images
self.updateUI() // do whatever you want to trigger the update of the UI
}
这是使用Swift 5 enum DownloadError: Error {
case notImage
case invalidStatusCode(URLResponse)
}
func download(_ url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
completion(.failure(error!))
return
}
guard 200..<300 ~= response.statusCode else {
completion(.failure(DownloadError.invalidStatusCode(response)))
return
}
guard let image = UIImage(data: data) else {
completion(.failure(DownloadError.notImage))
return
}
completion(.success(image))
}
}
枚举。如果您使用的是较早版本的Swift,则可以自己定义此枚举的简单形式:
Result
最后,值得注意其他一些选择:
将网络请求包装在异步enum Result<Success, Failure> {
case success(Success)
case failure(Failure)
}
子类中,并将其添加到Operation
设置为4的操作队列中。如果您对此方法感兴趣,我可以提供一些参考
使用Kingfisher之类的图像下载库。
使用maxConcurrentOperationCount
扩展名(例如Kingfisher提供)代替完全下载所有图像,而完全放弃“下载所有图像”过程,并转到一种模式只需指示您的图像视图以即时方式(或预取)异步检索图像即可。