我在一个数组中有10个urls,当其中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)
}我把这个放在操作/操作控制台里
> Downloading image 1
> Downloading image 2
> Downloading image 3
> Downloading image 4信号量(41269,0x70000ade5000) malloc:*对象0x1077d4750错误:未分配被释放的指针 信号量(41269,0x70000ade5000) malloc:*在malloc_error_break中设置一个断点以进行调试
我希望按顺序打印1,2,3,4
我知道我试图访问异步中的共享资源,但不确定如何解决这个问题。请指点
此外,如果我想一次下载4,4,2个任务,以便在我的输出中显示1,2,3,4,5,6,7,8,9,10,那么我如何与信号量一起使用它?
发布于 2019-09-16 00:41:24
您的标题是“按url的顺序下载图像”,但是您的代码片段并没有试图这样做。它似乎试图使用信号量一次将下载限制为四幅图像,但这并不能保证它们是正常的。
值得称赞的是,这段代码片段没有按顺序顺序下载它们,因为这会造成巨大的性能损失。同样好的是,此代码段将这种程度的并发限制在合理的水平上,从而避免耗尽工作线程或导致后一些请求超时。因此,使用信号量允许并发图像下载,但一次将其限制为4个的想法是一种很好的方法;如果您想要的话,我们只需要在最后对结果进行排序。
但在讨论之前,让我们先解决提供的代码片段中的一系列问题:
group.enter()和semaphore.wait() (这是正确的),但是只有当i是4 (不正确)时,才调用group.leave()和semaphore.signal()。您希望在每次迭代中都使用leave和signal。
显然,break调用也是不需要的。
因此,要修复这个“一次执行四次”的过程,可以简化以下代码:
让组= DispatchGroup()让队列= DispatchQueue.global(qos:.userInteractive)让信号量=DispatchSemaphore(值: 4) var num: Int = [] for i在1.10{ group.enter() semaphore.wait() queue.async() { // NB:group参数不需要打印(“下载图像(i)") //模拟网络等待Thread.sleep(forTimeInterval: 3) nums.append(i)打印(“Hola图像(i)") semaphore.signal() group.leave() }}group.notify(队列:.main) {print(Num)}
这将一次下载四个图像,并在它们全部完成后调用您的group.notify闭包。nums数组,但Array并不是线程安全的.因此,您应该将这些更新同步到该数组。实现这一目标的一种简单方法是将更新发送回主线程。(任何串行队列都可以,但是主线程可以正常工作。)
另外,由于不应该在主队列上调用wait,所以我建议您显式地将整个for循环分配给后台线程:
DispatchQueue.global(qos:.utility).async { let group = DispatchGroup() let queue = DispatchQueue.global(qos:.userInteractive) let信号量=DispatchSemaphore(值: 4) var num: Int = [] for i in 1.10{ group.enter() semaphore.wait() queue.async() {print(“下载图像(i)")//模拟网络等待Thread.sleep(forTimeInterval: 3) DispatchQueue.main.async { nums.append(i) print("Hola image (i)") } semaphore.signal() group.leave() }group.notify(队列:.main){打印(Num)}}
这就是现在正确的“一次做四次,当它完成时让我知道”。好了,现在我们正在正确下载所有的图像,让我们找出如何排序的结果。坦率地说,我认为,如果我们设想有一些图像下载方法,比如下载特定图像的方法,那么跟踪所发生的事情就更容易了:
func download(_ url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) { ... }然后,例程(a)下载图像,每次不超过4个;(b)按顺序返回结果,如下所示:
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] })
}
}
}你会这样称呼它:
downloadAllImages(urls) { images in
self.images = images
self.updateUI() // do whatever you want to trigger the update of the UI
}FWIW,“下载单个图像”例程可能如下所示:
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 5 Result枚举。如果您使用的是Swift的早期版本,您可以自己定义一种简单的表示方式:
enum Result<Success, Failure> {
case success(Success)
case failure(Failure)
}最后,值得注意的还有其他几种选择:
Operation子类中,并将它们添加到maxConcurrentOperationCount设置为4的操作队列中。如果您对这种方法感兴趣,我可以提供一些引用。UIImageView扩展(如翠鸟提供的),完全放弃“下载所有图像”过程,转而使用一种模式,您只需指示图像视图以及时的方式(或预取)异步检索图像。https://stackoverflow.com/questions/57938164
复制相似问题