我正在尝试使用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)不支持异步操作,因此我需要等待完整下载完成才能返回响应(请参阅上面引用的原始问题更多细节)。
答案 0 :(得分:11)
当使用var app = require('express')();
var http = require('http').Server(app);
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
调用自身异步的方法时,只要所有异步任务都已启动,该组就会完成,但不会等待它们完成。相反,您可以在进行异步调用之前手动调用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)
}
}
如果您不小心可能会死锁,如果从主线程完成,可以阻止UI等。更好的是,您可以使用dispatch_group_wait
来实现所需的行为。
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
}
}
}
}
对于Swift 2和Alamofire 3的回答,请参阅previous revision of this answer。
答案 1 :(得分:6)
在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
答案 2 :(得分:1)
代码完全按照你所说的去做。
对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
都会进入该组,并且在调用下载完成处理程序之前不会离开该组。
答案 3 :(得分:1)
使用此模式,最后一行将在其他任务完成时执行。
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
}