如何管理下载队列?

时间:2017-08-07 14:23:39

标签: ios swift grand-central-dispatch semaphore dispatch

我正在接受用户输入以从服务器下载文件。下载任务可以包括请求Web服务。

我期待这样的事情:

  

1)每当用户选择要下载或请求Web的文件时   服务,那么它应该被视为一个操作或任务块   并且应该进入将在应用程序全局管理的队列中   水平。
  2)同时如果队列为空则应该   自动开始执行当前任务   3)如果队列包含   任何操作然后它应该执行所有旧的操作   同步然后执行最后一个。

任何人都可以建议如何通过优化的方式来完成这项工作吗?

看一看我的尝试:

class func downloadChaptersFromDownloadQueue() {

    let gbm = GlobalMethods()

    for chapterDetail in gbm.downloadOpertationQueue.array.enumerated() {

        if chapterDetail.element.chapterdata.state == .non || chapterDetail.element.chapterdata.state == .paused || chapterDetail.element.chapterdata.state == .downloading {

            gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloading
                let s = DispatchSemaphore(value: 0)

                self.downloadActivty(courseId: chapterDetail.element.courseId, mod: chapterDetail.element.chapterdata, selectedIndexpath: chapterDetail.element.cellForIndexpath, success: { (result) in

                    if (result) {
                        if (WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .downloaded)) {
                            s.signal()

                            gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloaded
                            NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": 1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
                        }
                        else {
                            s.signal()
                            gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
                            NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
                        }
                    }
                    else {
                        _ = WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .non)

                        s.signal()

                        gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
                        NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
                    }
                })
                s.wait()
        }
    }
}

3 个答案:

答案 0 :(得分:3)

创建异步队列。首先使用调度组来跟踪完成的请求数,并在完成所有请求后得到通知(完全异步)。

接下来,将所有请求排入队列。每个请求都应该有一个唯一的标识符,以便您知道哪个请求已完成或失败(在您的情况下,chapterId& pageNumber应该足够了)。

立即执行所有请求(同样是异步),当每个请求完成时(通过完成块在主队列上),您将收到通知。应使用所有请求响应及其唯一标识符调用完成块。

示例:

class NetworkResponse {
    let data: Data?
    let response: URLResponse?
    let error: Error?

    init(data: Data?, response: URLResponse?, error: Error?) {
        self.data = data
        self.response = response
        self.error = error
    }
}


class NetworkQueue {
    static let instance = NetworkQueue()
    private let group = DispatchGroup()
    private let lock = DispatchSemaphore(value: 0)
    private var tasks = Array<URLSessionDataTask>()
    private var responses = Dictionary<String, NetworkResponse>()

    private init() {

    }

    public func enqueue(request: URLRequest, requestID: String) {

        //Create a task for each request and add it to the queue (we do not execute it yet). Every request that is created, we enter our group.

        self.group.enter();
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            //Only one thread can modify the array at any given time.
            objc_sync_enter(self)
            self.responses.updateValue(NetworkResponse(data: data, response: response, error: error), forKey: requestID)
            objc_sync_exit(self)

            //Once the request is complete, it needs to leave the group.
            self.group.leave()
        }

        //Add each task to the queue.
        self.tasks.append(task)
    }

    public func execute(completion: @escaping (_ responses: Dictionary<String, NetworkResponse>) -> Void) {

        //Get notified on the main queue when every single request is completed (they all happen asynchronously, but we get one notification)

        self.group.notify(queue: DispatchQueue.main) { 

            //Call our completion block with all the responses. Might be better to use a sorted dictionary or something here so that the responses are in order.. but for now, a Dictionary with unique identifiers will be fine.
            completion(self.responses)
        }

        //Execute every task in the queue.
        for task in self.tasks {
            task.resume()
        }

        //Clear all executed tasks from the queue.
        self.tasks.removeAll()
    }
}

编辑(使用您自己的代码):

class func downloadChaptersFromDownloadQueue() {


    let gbm = GlobalMethods()
    let group = DispatchGroup()
    let lock = NSLock()

    //Get notified when ALL tasks have completed.
    group.notify(queue: DispatchQueue.main) {
        print("FINISHED ALL TASKS -- DO SOMETHING HERE")
    }

    //Initially enter to stall the completion
    group.enter()

    defer {
        group.leave() //Exit the group to complete the enqueueing.
    }

    for chapterDetail in gbm.downloadOpertationQueue.array.enumerated() {

        if chapterDetail.element.chapterdata.state == .non || chapterDetail.element.chapterdata.state == .paused || chapterDetail.element.chapterdata.state == .downloading {

            gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloading

            //Enter the group for each downloadOperation
            group.enter()

            self.downloadActivty(courseId: chapterDetail.element.courseId, mod: chapterDetail.element.chapterdata, selectedIndexpath: chapterDetail.element.cellForIndexpath, success: { (result) in

                lock.lock()
                defer {
                    group.leave() //Leave the group when each downloadOperation is completed.
                }

                if (result) {
                    if (WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .downloaded)) {

                        gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloaded
                        lock.unlock()

                        NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": 1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
                    }
                    else {
                        gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
                        lock.unlock()

                        NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
                    }
                }
                else {
                    _ = WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .non)

                    gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
                    lock.unlock()

                    NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
                }
            })
        }
    }
}

同样,这是异步的,因为您不希望用户永远等待下载100页..

答案 1 :(得分:0)

对于像这样的任务,您需要做的第一件事是使用dispatch_async异步执行它们,以便它们位于不同的线程上,不会影响应用程序的性能(或冻结它)。

每当您的下载成功/失败时,您始终可以控制其完成块中接下来发生的事情。 (我建议您根据自己的需要使用递归来实现目标。)

希望这有帮助!

答案 2 :(得分:0)

要从服务器下载文件,通常要使用后台线程。 GCD比DispatchQueue.global().async { // this convenience initialiser for the global dispatch queue uses "DispatchQoS.QoSClass.default" } API更容易使用,因此我建议按如下方式使用它:

let concurrentQueue = DispatchQueue(label: "queuename", attributes: .concurrent)
concurrentQueue.async {

}

如果需要并发队列(如需求3中所指定),请使用以下命令:

{{1}}