Использование очереди последовательной отправки для вызова метода, который принимает блок завершения в качестве параметра

У меня есть метод:

-(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 для инкапсуляции каждого запроса. Сигнализируйте операцию как завершенную только после того, как вы вызвали свой блок завершения. Этот подход, вероятно, лучший и наименее подвержен ошибкам.

Другие вопросы по тегам