GCD:URLSession下载任务

问题描述

我需要下载大量文件-以前一次只能下载一个文件。当前的设计是,当用户下载单个文件时,将创建URLSession任务,并使用urlsession的委托方法记录进度/完成/失败。我的问题是,如何在此委托方法中保留调度组?我需要一次下载10个文件,在前十个文件完成后再开始下一个10个文件。现在,如果我将派遣组保留在委托方法中,则派遣组的等待将永远等待。到目前为止,这是我已经实现的:

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

在代表中:

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

这甚至可能吗?如果没有,我该如何实现?

解决方法

如果downloadAllDispatchQueue是串行队列,则您问题中的代码将死锁。当您调用wait时,它将阻塞该当前线程,直到它从另一个线程接收到leave调用为止。如果您尝试将leave分派到已经被wait调用阻塞的串行队列中,它将死锁。

解决方案是根本不将leave分派到队列。没有必要。只需直接从当前线程中调用它即可:

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

下载大量文件时,我们经常使用后台会话。参见Downloading Files in the Background。我们这样做是为了使下载即使在用户离开应用程序后仍会继续。

当您开始使用后台会话时,无需介绍这种“十批”逻辑。后台会话为您管理所有这些请求。在“十批”逻辑上分层只会带来不必要的复杂性和低效率。

相反,我们只实例化一个后台会话并提交所有请求,然后让后台会话从那里管理请求。它简单,高效,即使在用户离开应用程序后,也可以继续下载。如果下载的文件太多,以至于您需要像这样来管理它们,则最终用户很可能会厌倦此过程,并可能希望在请求完成时离开应用程序来执行其他操作。