Swift,dispatch_group_wait没有等待

时间:2016-01-25 00:28:37

标签: ios multithreading swift grand-central-dispatch

我正在尝试使用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)不支持异步操作,因此我需要等待完整下载完成才能返回响应(请参阅上面引用的原始问题更多细节)。

4 个答案:

答案 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_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都会进入该组,并且在调用下载完成处理程序之前不会离开该组。

答案 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
}