Перенос асинхронных вызовов в поток синхронной блокировки? - PullRequest
10 голосов
/ 21 декабря 2011

Я пишу модуль iOS, который в настоящее время отправляет электронное письмо асинхронно (с использованием делегатов). Он использует SKPSMTPMessage, который прекрасно работает. Моя проблема в том, что клиент хочет, чтобы код полностью блокировал поток до тех пор, пока электронное письмо не будет отправлено (или не будет отправлено). Таким образом, они в основном запрашивают синхронное решение, когда в настоящее время оно пытается отправить электронное письмо, а затем вернуться из этого блока кода до того, как электронное письмо было отправлено.

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

Я пробовал несколько разных методов, используя NSOperation s и NSThread, но, возможно, я что-то не так делаю, потому что каждый раз, когда я пытаюсь заблокировать основной поток, асинхронные вызовы делегатов все равно никогда не заканчиваются ( они возвращаются в основной поток или что-то?).

Любая информация или даже другие идеи приветствуются.

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

РЕДАКТИРОВАТЬ: Спасибо за все вклад. Как подсказывает один из ответов, я закончил тем, что использовал цикл while, который ждал возвращения делегатов, но позволил runLoop продолжить так же, как показано ниже:

while( ![messageDelegate hasFinishedOrFailed] ){
    // Allow the run loop to do some processing of the stream
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}

Ответы [ 3 ]

24 голосов
/ 21 декабря 2011

Я бы попробовал использовать семафоры отправки.Со страницы руководства для dispatch_semaphore_create(3):

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
    foo();
    dispatch_semaphore_signal(sema);
});

bar();

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
sema = NULL;

Вызов dispatch_semaphore_wait() будет блокироваться до завершения вызова dispatch_semaphore_signal().

4 голосов
/ 21 декабря 2011

Я не верю, что есть какой-то способ сделать это без изменения SKPSMTPMessage. На самом деле класс не использует отдельные потоки; вместо этого он использует NSStream совместно с циклом выполнения потока , чтобы избежать блокировки:

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                            forMode:NSRunLoopCommonModes];

Поток выступает в качестве входного источника для цикла выполнения ; тогда цикл выполнения свободен для обработки других событий, пока что-то не случится с потоком, и тогда поток уведомит своего делегата. Все еще происходит в оригинальной (основной) ветке; если вы попытаетесь заблокировать его самостоятельно, вы также заблокируете цикл выполнения, и он не сможет ничего сделать с потоком.

Другие уже отмечали, что блокировка основного потока - плохая идея; Помимо проблем с UX, система может завершить работу любого приложения, которое не отвечает на события в течение слишком длительного периода. Тем не менее, вы можете поместить всю настройку сообщения в фоновый режим, предоставив ей собственный цикл выполнения для потока, чтобы работать в ней, и заблокировать основной поток, используя GCD . К сожалению, я не могу придумать, как делегат мог сообщить, что это сделано без опроса.

dispatch_queue_t messageQueue;
messageQueue = dispatch_queue_create("com.valheru.messageQueue", NULL);

// dispatch_sync blocks the thread on which it's called
dispatch_sync(messageQueue, ^{
    [messageManager tryToDeliverMessage];
});
dispatch_release(messageQueue);

Где tryToDeliverMessage выглядит примерно так:

- (void) tryToDeliverMessage {
    // Create the message and let it run...

    // Poll a flag on the delegate
    while( ![messageDelegate hasFinishedOrFailed] ){
        // Allow the run loop to do some processing of the stream
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    }

    return;
}
2 голосов
/ 21 декабря 2011

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

...