Как управлять очередью загрузки?

99
10

Я беру пользовательский ввод для загрузки файлов с сервера. Задача загрузки может включать запрос веб-служб.

Я ожидаю что-то вроде этого:

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

спросил(а) 2021-01-25T16:21:27+03:00 4 месяца, 3 недели назад
1
Решение
63

Создайте асинхронную очередь. Сначала используйте диспетчерскую группу для отслеживания количества запросов и получения уведомления, когда все будет завершено (полностью асинхронно).

Затем введите все ваши запросы. Каждый запрос должен иметь уникальный идентификатор, чтобы вы знали, какой запрос завершен или сбой (в этом случае должно быть достаточно номера chapterId & pageNumber).

Выполняйте все запросы одновременно (опять же async), и вы будете уведомлены о завершении каждого из них (в главной очереди через ваш блок завершения). Блок завершения должен вызываться со всеми ответами на запросы и их уникальными идентификаторами.

Пример:

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

EDIT (используя собственный код):

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 страниц.

ответил(а) 2021-01-25T16:21:27+03:00 4 месяца, 3 недели назад
45

Для таких задач первое, что вам нужно сделать, это сделать их асинхронно с помощью dispatch_async, чтобы они находились в другом потоке и не влияли на производительность приложения (или блокировали его).

Всякий раз, когда ваши загрузки преуспевают/терпят неудачу, вы всегда можете контролировать, что будет дальше в этом блоке завершения. (Я предлагаю вам использовать рекурсию для того, чего вы пытаетесь достичь, поскольку это соответствует вашим потребностям).


Надеюсь это поможет!

ответил(а) 2021-01-25T16:21:27+03:00 4 месяца, 3 недели назад
44

Запросить область действия веб-службы в DispatchQueue

Синтаксис Swift:

DispatchQueue.main.async {
// your request code
}

ответил(а) 2021-01-25T16:21:27+03:00 4 месяца, 3 недели назад
44

Для загрузки файлов с сервера обычно используется фоновый поток. GCD намного проще в использовании, чем API Thread, поэтому я бы рекомендовал использовать его следующим образом:

DispatchQueue.global().async {
// this convenience initialiser for the global dispatch queue uses "DispatchQoS.QoSClass.default"
}

Если вы хотите, чтобы параллельная очередь, как указано в требовании 3, использовала следующее:

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

}

ответил(а) 2021-01-25T16:21:27+03:00 4 месяца, 3 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема