Swift, dispatch_group_wait is not waiting

I am trying to use a grand central dispatcher to wait for the file upload to complete before continuing. This question is a side issue of this: Swift (iOS), waiting for all images to complete before loading .

I'm just trying to figure out how to get dispatch_group_wait (or the like) to actually wait and not just continue until the download is complete. Note that if I use NSThread.sleepForTimeInterval instead of calling downloadImage, it waits just fine.

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 related to the HTTP POST request, and I use an HTTP server (Swifter) that does not support asynchronous operations, so I need to wait until the full download is complete before returning the answer (see the original question referenced above for more detailed information).

+5
source share
4 answers

When using dispatch_group_async to call methods that are themselves asynchronous, the group will exit immediately after all asynchronous tasks are started, but does not wait for them to complete. Instead, you can manually call dispatch_group_enter before making an asynchronous call, and then call dispatch_group_leave when the asynchronous call ends. Then dispatch_group_wait will behave as expected.

To accomplish this, first modify downloadImage to include the completion handler parameter:

 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 made a completion handler that returns an error code. Emphasize this as you see fit, but hopefully this illustrates this idea.

But by providing a completion handler, now that you are doing the downloads, you can create a group, an β€œenter” group, before you start each download, β€œleave” the group when the completion handler is called asynchronously.

But dispatch_group_wait may come to a standstill if you are not careful, it may block the user interface if it is made from the main thread, etc. Better, you can use dispatch_group_notify to achieve your desired behavior.

 func downloadImages(imageFilesOnServer: [AdFileInfo], completionHandler: (Int) -> ()) { let group = dispatch_group_create() var downloaded = 0 for serverFile in imageFilesOnServer { dispatch_group_enter(group) print("Start downloading \(serverFile.fileName)") self.downloadImage(serverFile) { error in if error == nil { downloaded += 1 } dispatch_group_leave(group) } } dispatch_group_notify(group, dispatch_get_main_queue()) { completionHandler(downloaded) } } 

And you would call it that:

 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 
+10
source

In 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

+6
source

The code does exactly what you tell it.

The dispatch_group_wait call will be blocked until the block inside the dispatch_group_async call is completed.

The block inside the dispatch_group_async call will end when the for loop completes. This will end almost immediately, since the bulk of the work done inside the downloadImage function is asynchronous.

This means that the for loop completes very quickly and this block executes (and dispatch_group_wait stops waiting) long before any of the actual downloads are completed.

I used dispatch_group_enter and dispatch_group_leave instead of dispatch_group_async .

I would change your code to something like the following (not verified, maybe 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 is included in the group and does not leave the group until the download completion handler is called.

+1
source

Using this template, the last line will be completed when the rest of the tasks are completed.

 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 } 
0
source

Source: https://habr.com/ru/post/1241373/


All Articles