2016-01-25 19 views
5

Sto tentando di utilizzare il grand central dispatch per attendere che i file finiscano il download prima di continuare. Questa domanda è uno spin-off di questo: Swift (iOS), waiting for all images to finish downloading before returning.Swift, dispatch_group_wait non in attesa

Sto semplicemente cercando di scoprire come ottenere dispatch_group_wait (o simile) per attendere effettivamente e non solo continuare prima che i download siano terminati. Si noti che se utilizzo NSThread.sleepForTimeInterval anziché chiamare downloadImage, è sufficiente attendere.

Cosa mi manca?

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)") 
      } 
     } 
    } 
} 

Nota: questi download sono in risposta a una richiesta HTTP POST e sto usando un server HTTP (Swifter) che non supporta operazioni asincrone, quindi ho bisogno di aspettare i download completi per completare prima di tornare una risposta (vedi la domanda originale di cui sopra per ulteriori dettagli).

risposta

10

Quando si utilizza dispatch_group_async per chiamare i metodi che sono, essi stessi, asincrono, il gruppo finirà non appena tutte le attività asincrone hanno iniziato, ma non aspettare che siano loro a finire. Invece, è possibile chiamare manualmente dispatch_group_enter prima di effettuare la chiamata asincrona e quindi chiamare dispatch_group_leave al termine della chiamata asincrona. Quindi dispatch_group_wait si comporterà come previsto.

per raggiungere questo obiettivo, però, prima modifica downloadImage da includere parametro handler completamento:

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) 
    } 
} 

Ho fatto che un gestore di completamento che passa di nuovo il codice di errore. Modificalo come ritieni opportuno, ma si spera che illustri l'idea.

Tuttavia, dopo aver fornito il gestore di completamento, ora, quando si eseguono i download, è possibile creare un gruppo, "immettere" il gruppo prima di iniziare ogni download, "lasciare" il gruppo quando il gestore di completamento viene chiamato in modo asincrono.

Ma dispatch_group_wait può deadlock se non stai attento, può bloccare l'interfaccia utente se fatto dal thread principale, ecc. Meglio, è possibile utilizzare dispatch_group_notify per ottenere il comportamento desiderato.

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) 
    } 
} 

E che ci si chiamarlo in questo modo:

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 
+0

Rob, ho un problema simile qui: http : //stackoverflow.com/questions/37201735/how-to-make-inner-async-request-complet-first-before-completing-outer-async-req pensi di poterti aiutare? – Pangu

+0

Ottima risposta, questo mi ha aiutato molto a fare qualcosa di simile con Firebase. – kelsheikh

+0

Il punto di ** 'dispatch_group_wait' può deadlock se non stai attento ** mi ha salvato! – fujianjin6471

1

Il codice sta facendo esattamente quello che stai dicendo.

La chiamata a dispatch_group_wait bloccherà fino a quando il blocco all'interno della chiamata a dispatch_group_async è terminato.

Il blocco all'interno della chiamata a dispatch_group_async termina quando il ciclo for viene completato. Questo verrà completato quasi immediatamente poiché la maggior parte del lavoro svolto all'interno della funzione downloadImage viene eseguita in modo asincrono.

Ciò significa che il ciclo for termina molto rapidamente e che il blocco è terminato (e dispatch_group_wait interrompe l'attesa) molto prima che uno dei download effettivi sia completato.

Vorrei utilizzare dispatch_group_enter e dispatch_group_leave anziché dispatch_group_async.

vorrei cambiare il codice a qualcosa come le seguenti (non testato, potrebbe essere errori di battitura):

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); 
     } 
    } 
} 

Questo cambiamento dovrebbe fare quello che ti serve. Ogni chiamata a downloadImage entra nel gruppo e non lascia il gruppo fino a quando non viene chiamato il gestore di completamento download.

0

Utilizzando questo modello, la linea finale verrà eseguita al termine delle altre attività.

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 
}