Использование очереди последовательной отправки для вызова метода, который принимает блок завершения в качестве параметра
У меня есть метод:
-(void) dataForRequest:(NSMutableURLRequest*)url withCallback:(void (^)(NSData* data))callbackBlock`
который получает данные из URL в фоновом режиме, а затем вызывает callbackBlock
с полученными данными.
Я вызываю это в последовательной очереди как:
dispatch_async(my_serial_queue(), ^(void) {
[dataForRequest:SOME_REQUEST withCallback:^(NSData *data) {
// do something with data here.
}];
});
Причина, по которой я делаю это в последовательной очереди, заключается в том, что я хочу, чтобы запросы вызывались по одному, В ПОРЯДКЕ, в котором они были сделаны.
Но проблема, с которой я сталкиваюсь, заключается в том, что запросы выполняются по порядку, но callbackBlock
то, что я передаю методу, НЕ вызывается по порядку.
Например, request_1
, request_2
а также request_3
отправляются в последовательную очередь в правильном порядке, но callBackBlock_1
, callBackBlock_2
а также callBackBlock_3
не выполняются в том же порядке.
Я понимаю, почему это происходит, но я не могу найти никакого решения для этого. Любая помощь будет оценена. Спасибо!
2 ответа
Вот решение, реализованное в Swift, основанное исключительно на GCD. Этот подход является чисто асинхронным, за исключением последнего утверждения dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
которая существует только для того, чтобы поддерживать основной поток до тех пор, пока не будут выполнены все задачи и продолжения.
Не должно быть слишком сложно перенести этот образец на Objective-C. Суть, вероятно, заключается в том, чтобы правильно импортировать ссылки на dispatch_groups.
Приведенный ниже код создает десять асинхронных задач с именами "1", "2", .. и "10". Продолжительность каждой задачи составляет от 0 до 8 секунд. Продолжение каждой задачи напечатает свое имя.
Таким образом, выходные данные должны быть такими, чтобы имена печатались в том же порядке, в котором они были запланированы, независимо от продолжительности. Может случиться так, что имена уже напечатаны, в то время как все еще выполняются задачи.
import Foundation
let queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL)
func task(duration: Double, completion: ()->()) {
let t = dispatch_time(DISPATCH_TIME_NOW, (Int64)(duration * Double(NSEC_PER_SEC)))
dispatch_after(t, dispatch_get_global_queue(0, 0)) {
completion()
}
}
func test(params: [String], continuation: ()->()) {
let g0 = dispatch_group_create()
dispatch_group_enter(g0)
var g_prev = g0
for s in params {
let g = dispatch_group_create()
dispatch_group_enter(g)
let t = Double(arc4random() % 8)
print("schedule task \"\(s)\" with duration \(t)")
let gp = g_prev
task(t) {
print("finished task \"\(s)\"")
dispatch_group_notify(gp, queue) { [g]
print(s)
dispatch_group_leave(g)
}
}
g_prev = g
}
dispatch_group_leave(g0)
dispatch_group_notify(g_prev, dispatch_get_global_queue(0, 0)) {
continuation()
}
}
let sem = dispatch_semaphore_create(0)
test(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) {
print("finished")
dispatch_semaphore_signal(sem)
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
Существует множество возможных реализаций, но основной принцип здесь заключается в том, что вы хотите, чтобы один запрос был завершен, прежде чем выдать следующий.
Как упоминалось в комментариях, вы можете создать NSOperation для инкапсуляции каждого запроса. Сигнализируйте операцию как завершенную только после того, как вы вызвали свой блок завершения. Этот подход, вероятно, лучший и наименее подвержен ошибкам.