GCD: URLSession download task

359 Views Asked by At

I have a requirement to download large number of files - previously only one file could be downloaded at a time. The current design is such that when the user downloads a single file, a URLSession task is created and the progress/completion/fail is recorded using the delegate methods for urlsession. My question is, how can I leave a dispatch group in this delegate method? I need to download 10 files at a time, start the next 10 when the previous ten finishes. Right now, if I leave the dispatch group in the delegate method, the dispatch group wait waits forever. Here's what I've implemented so far:

self.downloadAllDispatchQueue.async(execute: {
    self.downloadAllDispatchGroup = DispatchGroup()
    let maximumConcurrentDownloads: Int = 10
    var concurrentDownloads = 0
    for i in 0..<files.count
    {
        if self.cancelDownloadAll {
            return
        }
            if concurrentDownloads >= maximumConcurrentDownloads{
                self.downloadAllDispatchGroup.wait()
                concurrentDownloads = 0
            }
            if let workVariantPart = libraryWorkVariantParts[i].workVariantPart {
                concurrentDownloads += 1
                self.downloadAllDispatchGroup.enter()
                //call method for download
            }
    }
    self.downloadAllDispatchGroup!.notify(queue: self.downloadAllDispatchQueue, execute: {
        DispatchQueue.main.async {
            
        }
    })
})

In the delegates:

func downloadDidFinish(_ notification: Notification){
        if let dispatchGroup = self.downloadAllDispatchGroup {
            self.downloadAllDispatchQueue.async(execute: {
                dispatchGroup.leave()
            })
        }
}

Is this even possible? If not, how can I achieve this?

1

There are 1 best solutions below

6
On

If downloadAllDispatchQueue is a serial queue, the code in your question will deadlock. When you call wait, it blocks that current thread until it receives the leave call(s) from another thread. If you try to dispatch the leave to a serial queue that is already blocked with a wait call, it will deadlock.

The solution is to not dispatch the leave to the queue at all. There is no need for that. Just call it directly from the current thread:

func downloadDidFinish(_ notification: Notification) {
    downloadAllDispatchGroup?.leave()
}

When downloading a large number of files, we often use a background session. See Downloading Files in the Background. We do this so downloads continue even after the user leaves the app.

When you start using background session, there is no need to introduce this “batches of ten” logic. The background session manages all of these requests for you. Layering on a “batches of ten” logic only introduces unnecessary complexities and inefficiencies.

Instead, we just instantiate a single background session and submit all of the requests, and let the background session manage the requests from there. It is simple, efficient, and offers the ability to continue downloads even after the user leaves the app. If you are downloading so many files that you feel like you need to manage them like this, it is just as likely that the end user will get tired of this process and may want to leave the app to do other things while the requests finish.