Swift, dispatch_group_wait 没等
Swift, dispatch_group_wait not waiting
我正在尝试使用 grand central dispatch 等待文件完成下载后再继续。这个问题是这个问题的衍生问题:Swift (iOS), waiting for all images to finish downloading before returning。
我只是想找出如何让 dispatch_group_wait(或类似的)真正等待,而不是在下载完成之前继续。请注意,如果我使用 NSThread.sleepForTimeInterval 而不是调用 downloadImage,它会等待得很好。
我错过了什么?
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)")
}
}
}
}
注意:这些下载是为了响应 HTTP POST 请求而我使用的是不支持异步操作的 HTTP 服务器 (Swifter),因此我需要等待完整下载完成在返回响应之前(有关更多详细信息,请参阅上面引用的原始问题)。
代码完全按照您的指示执行。
对 dispatch_group_wait
的调用将阻塞,直到对 dispatch_group_async
的调用中的块完成。
dispatch_group_async
调用中的块将在 for
循环完成时完成。这几乎会立即完成,因为 downloadImage
函数内完成的大部分工作是异步完成的。
这意味着 for
循环非常快地完成并且该块在任何实际下载完成之前很久就已完成(并且 dispatch_group_wait
停止等待)。
我会使用 dispatch_group_enter
和 dispatch_group_leave
而不是 dispatch_group_async
。
我会将您的代码更改为如下所示(未经测试,可能是拼写错误):
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);
}
}
}
此更改应该可以满足您的需要。每次调用 downloadImage
都会进入该组,并且在调用下载完成处理程序之前不会离开该组。
使用此模式,最后一行将在其他任务完成时执行。
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
}
当使用 dispatch_group_async
调用本身是异步的方法时,该组将在所有异步任务启动后立即完成,但不会等待它们完成。相反,您可以在进行异步调用之前手动调用 dispatch_group_enter
,然后在异步调用完成时调用 dispatch_group_leave
。然后 dispatch_group_wait
现在将按预期运行。
不过,要完成此操作,首先更改 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)
}
}
我已经将其作为一个完成处理程序来传回错误代码。调整你认为合适的,但希望它能说明这个想法。
但是,在提供了完成处理程序之后,现在,当您进行下载时,您可以创建一个组,"enter" 开始每次下载之前的组,"leave" 完成时的组处理程序被异步调用。
但是 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
}
}
}
}
你可以这样称呼它:
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
对于 Swift 2 和 Alamofire 3 的回答,参见 previous revision of this answer。
在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
我正在尝试使用 grand central dispatch 等待文件完成下载后再继续。这个问题是这个问题的衍生问题:Swift (iOS), waiting for all images to finish downloading before returning。
我只是想找出如何让 dispatch_group_wait(或类似的)真正等待,而不是在下载完成之前继续。请注意,如果我使用 NSThread.sleepForTimeInterval 而不是调用 downloadImage,它会等待得很好。
我错过了什么?
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)")
}
}
}
}
注意:这些下载是为了响应 HTTP POST 请求而我使用的是不支持异步操作的 HTTP 服务器 (Swifter),因此我需要等待完整下载完成在返回响应之前(有关更多详细信息,请参阅上面引用的原始问题)。
代码完全按照您的指示执行。
对 dispatch_group_wait
的调用将阻塞,直到对 dispatch_group_async
的调用中的块完成。
dispatch_group_async
调用中的块将在 for
循环完成时完成。这几乎会立即完成,因为 downloadImage
函数内完成的大部分工作是异步完成的。
这意味着 for
循环非常快地完成并且该块在任何实际下载完成之前很久就已完成(并且 dispatch_group_wait
停止等待)。
我会使用 dispatch_group_enter
和 dispatch_group_leave
而不是 dispatch_group_async
。
我会将您的代码更改为如下所示(未经测试,可能是拼写错误):
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);
}
}
}
此更改应该可以满足您的需要。每次调用 downloadImage
都会进入该组,并且在调用下载完成处理程序之前不会离开该组。
使用此模式,最后一行将在其他任务完成时执行。
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
}
当使用 dispatch_group_async
调用本身是异步的方法时,该组将在所有异步任务启动后立即完成,但不会等待它们完成。相反,您可以在进行异步调用之前手动调用 dispatch_group_enter
,然后在异步调用完成时调用 dispatch_group_leave
。然后 dispatch_group_wait
现在将按预期运行。
不过,要完成此操作,首先更改 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)
}
}
我已经将其作为一个完成处理程序来传回错误代码。调整你认为合适的,但希望它能说明这个想法。
但是,在提供了完成处理程序之后,现在,当您进行下载时,您可以创建一个组,"enter" 开始每次下载之前的组,"leave" 完成时的组处理程序被异步调用。
但是 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
}
}
}
}
你可以这样称呼它:
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
对于 Swift 2 和 Alamofire 3 的回答,参见 previous revision of this answer。
在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