[英]Swift, dispatch_group_wait not waiting
I am trying to use grand central dispatch to wait for files to finish download before continuing. 我正在尝试使用宏中央调度等待文件完成下载后再继续。 This question is a spin-off from this one: Swift (iOS), waiting for all images to finish downloading before returning .
这个问题是从这个问题衍生出来的: Swift(iOS),等待所有图像在返回之前完成下载 。
I am simply trying to find out how to get dispatch_group_wait (or similar) to actually wait and not just continue before the downloads have finished. 我只是试图找出如何让dispatch_group_wait(或类似)实际等待,而不是在下载完成之前继续。 Note that if I use NSThread.sleepForTimeInterval instead of calling downloadImage, it waits just fine.
请注意,如果我使用NSThread.sleepForTimeInterval而不是调用downloadImage,它等待就好了。
What am I missing? 我错过了什么?
class ImageDownloader {
var updateResult = AdUpdateResult()
private let fileManager = NSFileManager.defaultManager()
private let imageDirectoryURL = NSURL(fileURLWithPath: Settings.adDirectory, isDirectory: true)
private let group = dispatch_group_create()
private let downloadQueue = dispatch_queue_create("com.acme.downloader", DISPATCH_QUEUE_SERIAL)
func downloadImages(imageFilesOnServer: [AdFileInfo]) {
dispatch_group_async(group, downloadQueue) {
for serverFile in imageFilesOnServer {
print("Start downloading \(serverFile.fileName)")
//NSThread.sleepForTimeInterval(3) // Using a sleep instead of calling downloadImage makes the dispatch_group_wait below work
self.downloadImage(serverFile)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // This does not wait for downloads to finish. Why?
print("All Done!") // It gets here too early!
}
private func downloadImage(serverFile: AdFileInfo) {
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
self.updateResult.filesDownloaded++
print("Done downloading \(serverFile.fileName)")
}
}
}
}
Note: these downloads are in response to an HTTP POST request and I am using an HTTP server (Swifter) which does not support asynchronous operations, so I do need to wait for the full downloads to complete before returning a response (see original question referenced above for more details). 注意:这些下载是对HTTP POST请求的响应,我使用的HTTP服务器(Swifter)不支持异步操作,因此我需要等待完整下载完成才能返回响应(请参阅原始问题引用上面有更多细节)。
When using dispatch_group_async
to call methods that are, themselves, asynchronous, the group will finish as soon as all of the asynchronous tasks have started, but will not wait for them to finish. 当使用
dispatch_group_async
调用自身异步的方法时,只要所有异步任务都已启动,该组就会完成,但不会等待它们完成。 Instead, you can manually call dispatch_group_enter
before you make the asynchronous call, and then call dispatch_group_leave
when the asynchronous call finish. 相反,您可以在进行异步调用之前手动调用
dispatch_group_enter
,然后在异步调用完成时调用dispatch_group_leave
。 Then dispatch_group_wait
will now behave as expected. 然后
dispatch_group_wait
现在将按预期运行。
To accomplish this, though, first change downloadImage
to include completion handler parameter: 但要实现此目的,首先将
downloadImage
更改为包含完成处理程序参数:
private func downloadImage(serverFile: AdFileInfo, completionHandler: (NSError?)->()) {
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
print("Done downloading \(serverFile.fileName)")
}
completionHandler(error)
}
}
I've made that a completion handler that passes back the error code. 我已经做了一个传递错误代码的完成处理程序。 Tweak that as you see fit, but hopefully it illustrates the idea.
根据你的需要调整,但希望它能说明这个想法。
But, having provided the completion handler, now, when you do the downloads, you can create a group, "enter" the group before you initiate each download, "leave" the group when the completion handler is called asynchronously. 但是,提供完成处理程序后,现在,当您执行下载时,您可以创建一个组,在启动每次下载之前“输入”该组,在异步调用完成处理程序时“离开”该组。
But dispatch_group_wait
can deadlock if you're not careful, can block the UI if done from the main thread, etc. Better, you can use dispatch_group_notify
to achieve the desired behavior. 但是如果你不小心,
dispatch_group_wait
可以死锁,如果从主线程完成,可以阻止UI等等。更好的是,你可以使用dispatch_group_notify
来实现所需的行为。
func downloadImages(_ imageFilesOnServer: [AdFileInfo], completionHandler: @escaping (Int) -> ()) {
let group = DispatchGroup()
var downloaded = 0
group.notify(queue: .main) {
completionHandler(downloaded)
}
for serverFile in imageFilesOnServer {
group.enter()
print("Start downloading \(serverFile.fileName)")
downloadImage(serverFile) { error in
defer { group.leave() }
if error == nil {
downloaded += 1
}
}
}
}
And you'd call it like so: 你会这样称呼它:
downloadImages(arrayOfAdFileInfo) { downloaded in
// initiate whatever you want when the downloads are done
print("All Done! \(downloaded) downloaded successfully.")
}
// but don't do anything contingent upon the downloading of the images here
For Swift 2 and Alamofire 3 answer, see previous revision of this answer . 对于Swift 2和Alamofire 3的答案,请参阅此答案的上一版本 。
In Swift 3... 在Swift 3 ......
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
// do something, including background threads
dispatchGroup.leave()
dispatchGroup.notify(queue: DispatchQueue.main) {
// completion code
}
https://developer.apple.com/reference/dispatch/dispatchgroup https://developer.apple.com/reference/dispatch/dispatchgroup
The code is doing exactly what you are telling it to. 代码完全按照您的要求进行操作。
The call to dispatch_group_wait
will block until the block inside the call to dispatch_group_async
is finished. 对
dispatch_group_wait
的调用将一直阻塞,直到对dispatch_group_async
的调用内的块完成为止。
The block inside the call to dispatch_group_async
will be finished when the for
loop completes. 当
for
循环完成时,将调用dispatch_group_async
调用内的块。 This will complete almost immediately since the bulk of the work being done inside the downloadImage
function is being done asynchronously. 这将几乎立即完成,因为在
downloadImage
函数内完成的大部分工作是异步完成的。
This means the for
loop finishes very quickly and that block is done (and dispatch_group_wait
stops waiting) long before any of the actual downloads are completed. 这意味着
for
循环很快完成,并且在任何实际下载完成之前很久就完成了阻塞(并且dispatch_group_wait
停止等待)。
I would make use of dispatch_group_enter
and dispatch_group_leave
instead of dispatch_group_async
. 我会使用
dispatch_group_enter
和dispatch_group_leave
而不是dispatch_group_async
。
I would change your code to something like the following (not tested, could be typos): 我会将您的代码更改为以下内容(未经过测试,可能是拼写错误):
class ImageDownloader {
var updateResult = AdUpdateResult()
private let fileManager = NSFileManager.defaultManager()
private let imageDirectoryURL = NSURL(fileURLWithPath: Settings.adDirectory, isDirectory: true)
private let group = dispatch_group_create()
private let downloadQueue = dispatch_queue_create("com.acme.downloader", DISPATCH_QUEUE_SERIAL)
func downloadImages(imageFilesOnServer: [AdFileInfo]) {
dispatch_async(downloadQueue) {
for serverFile in imageFilesOnServer {
print("Start downloading \(serverFile.fileName)")
//NSThread.sleepForTimeInterval(3) // Using a sleep instead of calling downloadImage makes the dispatch_group_wait below work
self.downloadImage(serverFile)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // This does not wait for downloads to finish. Why?
print("All Done!") // It gets here too early!
}
private func downloadImage(serverFile: AdFileInfo) {
dispatch_group_enter(group);
let destinationPath = imageDirectoryURL.URLByAppendingPathComponent(serverFile.fileName)
Alamofire.download(.GET, serverFile.imageUrl) { temporaryURL, response in return destinationPath }
.response { _, _, _, error in
if let error = error {
print("Error downloading \(serverFile.fileName): \(error)")
} else {
self.updateResult.filesDownloaded++
print("Done downloading \(serverFile.fileName)")
}
dispatch_group_leave(group);
}
}
}
This change should do what you need. 这种改变应该做你需要的。 Each call to
downloadImage
enters the group and it doesn't leave the group until the download completion handler is called. 每次调用
downloadImage
进入该组,并且在调用下载完成处理程序之前不会离开该组。
Using this pattern, the final line will execute when the other tasks are finished. 使用此模式,最后一行将在其他任务完成时执行。
let group = dispatch_group_create()
dispatch_group_enter(group)
// do something, including background threads
dispatch_group_leave(group) // can be called on a background thread
dispatch_group_enter(group)
// so something
dispatch_group_leave(group)
dispatch_group_notify(group, mainQueue) {
// completion code
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.