简体   繁体   English

Swift,dispatch_group_wait没有等待

[英]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_enterdispatch_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM