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_enterdispatch_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