Как заставить один метод ждать завершения другого (асинхронного) метода, прежде чем продолжить выполнение?

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

Вот резюме: у меня есть два метода в моем классе, метод1 и метод2. Мне нужно вызвать асинхронную функцию внутри метода1. Затем код продолжает выполняться и достигает метода2. Но в методе 2 есть случаи, когда мне нужно использовать результат этого асинхронного вызова в методе 1, поэтому мне нужно убедиться, что асинхронный вызов в методе 1 завершился, прежде чем переходить к остальной части метода 2.

Я знаю, что один способ — использовать семафоры, а другой — использовать блоки завершения. Но я хочу сделать это наиболее общим способом, потому что будут другие методы, похожие на метод2, которым снова нужно будет дождаться завершения асинхронного вызова в методе1, прежде чем продолжить выполнение. Также по той же причине я не могу просто вызвать асинхронную функцию внутри самого метода2 и поместить остальную часть метода2 в его блок завершения.

Вот примерное представление о том, что я пытаюсь сделать. Я был бы признателен, если бы кто-нибудь добавил в этот псевдокод блоки завершения, чтобы я мог ясно видеть, как все будет работать. Кстати, метод1 и метод2 (и все остальные методы в этом классе) находятся в одном потоке (но не в основном потоке).

@implementation someClass

-(void)method1 {
    for (each item in the ivar array) {
        if (condition C is met on that item) {
            some_independent_async_call(item, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(int result, int *someArray) {
                if (result > 0) {
                    // The async call worked correctly, we will need someArray
                }
                else {
                    // We didn't get someArray that we wanted.
                }
            });
        }
    }
}

-(void)method2 {
    // Select one item in the ivar array based on some criteria.
    if (condition C is met on that item) {
        // Wait for some_independent_async_call() in method1 to complete.
        // Then use someArray in the rest of the code.
    }
    else {
        // Simply continue the rest of the code. 
    }
}

@end

Обновление: я знаю, что могу подать сигнал семафору после завершения асинхронного вызова, и я могу дождаться того же семафора в методе2, но вместо этого я хочу использовать блок завершения, потому что я думаю, что это было бы более общим, особенно если есть другие методы, похожие на метод2 в этом классе. Может кто-нибудь добавить блоки завершения в этот код, так как у меня возникают проблемы с его работой?


person makingCodeWork    schedule 03.05.2016    source источник
comment
Ваша логика цикла for наводит меня на мысль, что это может быть то, что вы ищете: Использование групп рассылки — блоки группировки позволяют осуществлять агрегированную синхронизацию. Ваше приложение может отправлять несколько блоков и отслеживать их завершение, даже если они могут выполняться в разных очередях. Такое поведение может быть полезно, когда прогресс невозможен, пока не будут выполнены все указанные задачи.   -  person Phillip Mills    schedule 03.05.2016
comment
Спасибо, но у меня есть только один блок, который нужно завершить, и несколько мест, где нужно использовать его результат. Не могли бы вы добавить к приведенному выше псевдокоду, как в этом случае будут работать группы отправки?   -  person makingCodeWork    schedule 03.05.2016
comment
Ваш асинхронный вызов находится внутри цикла. Вы имеете в виду, что условие C верно только для одного элемента?   -  person Phillip Mills    schedule 03.05.2016
comment
Нет, на самом деле это может быть верно для нескольких элементов, и нам нужно вызывать этот асинхронный метод для каждого из этих элементов. Позже, в методе 2, мы выбираем только один элемент, и если он имеет это условие C, нам нужно использовать какой-то массив для этого конкретного элемента из асинхронного вызова в методе 1.   -  person makingCodeWork    schedule 03.05.2016
comment
Хорошо... вот почему я предложил группы. Если у вас запущено несколько асинхронных операций, я предположил, что вы хотите, чтобы они все завершились до входа в метод2.   -  person Phillip Mills    schedule 03.05.2016


Ответы (2)


Судя по вашему коду, похоже, что вы контролируете асинхронную отправку.

Вместо some_independent_async_call используйте dispatch_sync, что заблокирует выполнение в текущем потоке до тех пор, пока данный блок не завершится.

с использованием dispatch_sync в Grand Central Dispatch

Однако, если у вас нет контроля над асинхронным вызовом, и вы фактически вызываете метод для объекта, который затем переходит и вызывает dispatch_async; у вас нет выбора, чем использовать блок завершения, шаблон обратного вызова или семафор, как вы указали

person A O    schedule 03.05.2016
comment
Спасибо. У меня нет контроля над асинхронным вызовом, это просто асинхронный метод откуда-то еще, который мне нужно использовать. Не могли бы вы показать мне, как использовать блок завершения здесь, поскольку я, кажется, не понимаю это правильно, когда пытаюсь? - person makingCodeWork; 03.05.2016
comment
Вы должны просто вызвать method2 из отправленного блока method1 в конце блока - person A O; 05.05.2016

Итак, если я правильно понял вашу проблему, у вас есть список «Элементов» и одна асинхронная задача. Задача принимает параметр item и вычисляет некоторый «результат» (массив целых чисел).

Теперь для каждого «элемента» будет оцениваться логическое значение, определяющее, следует ли запускать задачу с этим элементом в качестве аргумента.

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

Что ж, это, безусловно, можно реализовать с помощью групп отправки, обработчиков завершения, NSOperations и тому подобного. Но это быстро станет довольно сложным и подверженным ошибкам, особенно если вы хотите обрабатывать ошибки и, возможно, реализовать средства для отмены задачи, если это необходимо. Но поскольку с «Фьючерсами» это становится невероятно просто, я предлагаю это решение.

Обратите внимание, что «фьючерсы» не включены в стандартную библиотеку Swift. Тем не менее, есть несколько доступных сторонних библиотек. Здесь я буду использовать "подобные Scala" фьючерсы. Обратите внимание, что это также может быть реализовано с помощью «Promises» аналогичным образом. См. также вики Фьючерсы и обещания.

Используя фьючерсы, задача имеет следующую подпись:

typealias TaskType = (Item) -> Future<SomeResult>

Будущее — это «заполнитель» для значения, которое будет вычислено позже базовой асинхронной задачей. Задача также может завершиться ошибкой и вместо значения вернуть ошибку. То есть будущее будет в конечном итоге завершено либо с результатом, либо с ошибкой.

Пусть задача будет:

func task(item: Item) -> Future<SomeResult> {
    let promise = Promise<SomeResult>()
    fetchSomeResult(item.url) { (result, error) in
        if let result = result {
            promise.fulfill(result)
        } else {
            promise.reject(error!)
        }
    }
    return promise.future!
}

Кажется проще отфильтровать массив, чтобы получить фактические «Элементы», которые запускают задачу:

let activeItems = items.filter { $0.foo == something }

Получить массив фьючерсов:

let futures = activeItems.map { task($0) } 

Приведенный выше оператор запускает асинхронные задачи, каждая из которых соответствует своему элементу и возвращает массив фьючерсов с типом [Future<SomeResult>]. В настоящее время фьючерсы еще не завершены. Это произойдет в конечном итоге для каждого будущего, когда основная задача завершится.

Теперь добавьте продолжение к каждому будущему, которое вызывается при успешном выполнении задачи, а также добавьте обработчик ошибок, который вызывается при их возникновении:

futures.forEach { future in
    future.map { result in
        // This will be entered for each task, once it 
        // finished successfully.
        // Handle result (should be your array of ints)
        // This is where you implement the "method2" part
        // but for each item separately.
        // Note: if you need the "item" as well, use a
        // task which returns a tuple:
        // task(item: Item) -> Future<(Item, SomeResult)>
    }.onFailure { error in
       // handle error. This is an error returned from the task.
    }
}
person CouchDeveloper    schedule 03.05.2016
comment
Спасибо за подробное описание того, как использовать фьючерсы в этом коде. Боюсь, я не могу использовать Swift для этой конкретной задачи, но я буду иметь это в виду на будущее! - person makingCodeWork; 04.05.2016