【问题标题】:How to manage download queue?如何管理下载队列?
【发布时间】:2017-08-07 14:23:38
【问题描述】:

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

我期待这样的事情:

1) 每当用户选择要下载或请求网络的文件时 服务,那么它应该被视为一个操作或任务块 并且应该进入队列,该队列将在应用程序中进行全局管理 水平。
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()
        }
    }
}

【问题讨论】:

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


    【解决方案1】:

    创建一个异步队列。首先使用调度组来跟踪完成了多少请求,并在所有请求完成时收到通知(完全异步)。

    接下来,将所有请求排入队列。每个请求都应该有一个唯一的标识符,以便您知道哪个请求完成或失败(您的案例中的 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 个页面..

    【讨论】:

    • 我对这两个请求都使用了 alamofire
    • Alamorefire 拥有startRequestsImmediately。将其设置为 false 以便在所有请求都排队之前不会立即开始请求。否则,您将不得不更改要提前通知的逻辑并立即将所有请求排入队列。
    • 实际上,我确实有一个单独的服务调用函数。考虑到这一点,我只需要将函数添加到队列中。或者根据下载请求创建模型
    • 你能告诉我怎么称呼这个
    • 我不知道您的其余代码是什么样的。我所知道的是,您需要一个将被调用的完成块以及一种将所有请求排队的方法,然后上述模式将起作用。你可以像URLSessionDataTask一样使用alamofire对请求进行排队。
    【解决方案2】:

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

    每当您的下载成功/失败时,您始终可以控制在其完成块中接下来会发生什么。 (我建议您使用递归来满足您的需求)。

    希望这会有所帮助!

    【讨论】:

    • 谢谢,但我认为这还不够。你能简单介绍一下吗?
    • 当然可以,但我需要更多关于你正在做什么的详细信息来帮助你。
    猜你喜欢
    • 1970-01-01
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 2011-03-07
    • 2023-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多