Почему мы не можем использовать dispatch_sync в текущей очереди?

Я столкнулся с сценарием, в котором у меня был обратный вызов делегата, который может произойти либо в основном streamе, либо в другом streamе, и я не знаю, до StoreKit.framework исполнения (используя StoreKit.framework ).

У меня также был код пользовательского интерфейса, который мне нужно было обновить в этом обратном вызове, который должен был произойти до выполнения функции, поэтому моя первоначальная мысль заключалась в том, чтобы иметь такую ​​функцию:

 -(void) someDelegateCallback:(id) sender { dispatch_sync(dispatch_get_main_queue(), ^{ // ui update code here }); // code here that depends upon the UI getting updated } 

Это отлично работает, когда выполняется на фоновом streamе. Однако при выполнении в основном streamе программа заходит в тупик.

Это само по себе кажется мне интересным, если я прочитаю документы для dispatch_sync правильно, тогда я ожидаю, что он просто выполнит блок прямо, не беспокоясь о планировании его в runloop, как сказано здесь :

В качестве оптимизации эта функция, когда это возможно, вызывает блок на текущем streamе.

Но это не слишком большая сделка, это просто означает немного больше ввода текста, что привело меня к такому подходу:

 -(void) someDelegateCallBack:(id) sender { dispatch_block_t onMain = ^{ // update UI code here }; if (dispatch_get_current_queue() == dispatch_get_main_queue()) onMain(); else dispatch_sync(dispatch_get_main_queue(), onMain); } 

Однако это кажется немного отсталым. Было ли это ошибкой в ​​создании GCD, или есть что-то, чего мне не хватает в документах?

    Я нашел это в документации (последняя глава) :

    Не вызывайте функцию dispatch_sync из задачи, которая выполняется в той же очереди, что и в вызове функции. Это приведет к блокировке очереди. Если вам нужно отправить в текущую очередь, сделайте это асинхронно, используя функцию dispatch_async.

    Кроме того, я следил за ссылкой, которую вы предоставили, и в описании dispatch_sync я читал это:

    Вызов этой функции и таргетинг на текущую очередь приводит к взаимоблокировке.

    Поэтому я не думаю, что это проблема с GCD, я думаю, что единственным разумным подходом является тот, который вы изобрели после обнаружения проблемы.

    dispatch_sync выполняет две функции:

    1. очередь на блок
    2. блокирует текущий stream, пока блок не закончит работу

    Учитывая, что основной stream является последовательной очередью (что означает, что он использует только один stream), следующий оператор:

     dispatch_sync(dispatch_get_main_queue(), ^(){/*...*/}); 

    приведет к следующим событиям:

    1. dispatch_sync переводит блок в основную очередь.
    2. dispatch_sync блокирует stream главной очереди, пока блок не завершит выполнение.
    3. dispatch_sync ждет навсегда, потому что stream, в котором предполагается запустить блок, блокируется.

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

    Следующий подход:

     if (queueA == dispatch_get_current_queue()){ block(); } else { dispatch_sync(queueA,block); } 

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

     dispatch_sync(queueA, ^{ dispatch_sync(queueB, ^{ // dispatch_get_current_queue() is B, but A is blocked, // so a dispatch_sync(A,b) will deadlock. dispatch_sync(queueA, ^{ // some task }); }); }); 

    Для сложных случаев данные чтения / записи ключа-значения в очереди отправки:

     dispatch_queue_t workerQ = dispatch_queue_create("com.meh.sometask", NULL); dispatch_queue_t funnelQ = dispatch_queue_create("com.meh.funnel", NULL); dispatch_set_target_queue(workerQ,funnelQ); static int kKey; // saves string "funnel" in funnelQ CFStringRef tag = CFSTR("funnel"); dispatch_queue_set_specific(funnelQ, &kKey, (void*)tag, (dispatch_function_t)CFRelease); dispatch_sync(workerQ, ^{ // is funnelQ in the hierarchy of workerQ? CFStringRef tag = dispatch_get_specific(&kKey); if (tag){ dispatch_sync(funnelQ, ^{ // some task }); } else { // some task } }); 

    Объяснение:

    • Я создаю очередь workerQ которая указывает на очередь funnelQ . В реальном коде это полезно, если у вас есть несколько «рабочих» очередей, и вы хотите возобновить / приостановить все сразу (что достигается путем возобновления / обновления их целевой очереди funnelQ ).
    • Я могу направить мои рабочие очереди в любой момент времени, поэтому, чтобы узнать, были ли они перенаправлены или нет, я funnelQ словом «воронка».
    • По дороге я dispatch_sync , и по какой-то причине я хочу dispatch_sync в funnelQ , но избегая dispatch_sync в текущей очереди, поэтому я проверяю тег и действую соответственно. Поскольку get получает иерархию, значение не будет найдено в workerQ но оно будет найдено в funnelQ . Это способ выяснить, является ли какая-либо очередь в иерархии той, где мы сохранили значение. И, следовательно, чтобы предотвратить dispatch_sync в текущей очереди.

    Если вам интересно о функциях, которые читают / записывают контекстные данные, есть три:

    • dispatch_queue_set_specific : запись в очередь.
    • dispatch_queue_get_specific : чтение из очереди.
    • dispatch_get_specific : функция удобства для чтения из текущей очереди.

    Ключ сравнивается с указателем и никогда не разыгрывается. Последний параметр в установщике является деструктором для отпускания ключа.

    Если вам интересно, «указывая одну очередь на другую», это означает именно это. Например, я могу указать очередь A в основную очередь, и это приведет к тому, что все блоки в очереди A будут запущены в основной очереди (обычно это делается для обновлений пользовательского интерфейса).

    Я знаю, откуда происходит ваше замешательство:

    В качестве оптимизации эта функция, когда это возможно, вызывает блок на текущем streamе.

    Осторожно, это говорит о текущей теме .

    Тема: = Очередь

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

    Оптимизация этого предложения говорит о streamах, а не о очередях. Например, у вас есть две последовательные очереди, QueueA и QueueB и теперь вы делаете следующее:

     dispatch_async(QueueA, ^{ someFunctionA(...); dispatch_sync(QueueB, ^{ someFunctionB(...); }); }); 

    Когда QueueA запускает блок, он временно будет иметь stream, любой stream. someFunctionA(...) выполнит в этом streamе. Теперь, выполняя синхронную отправку, QueueA не может ничего сделать, он должен дождаться завершения отправки. QueueB с другой стороны, также потребуется stream для запуска своего блока и выполнения someFunctionB(...) . Таким образом, либо QueueA временно приостанавливает свой stream, и QueueB использует какой-либо другой stream для запуска блока или QueueA передает свой stream на QueueB (в конце концов, он не понадобится, пока синхронная отправка не завершится), а QueueB напрямую использует текущий stream QueueA .

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

    Но dispatch_sync() все равно не может произойти с одной и той же очередью (тот же stream, да, та же очередь, нет). Это потому, что очередь будет выполнять блок за блоком и когда он в настоящее время выполняет блок, он не будет выполнять другой, пока это не будет выполнено. Таким образом, он выполняет BlockA и BlockA делает dispatch_sync() BlockB в той же очереди. В очереди не будет работать BlockB пока он все еще запускает BlockA , но запуск BlockA не будет продолжаться до BlockB пор, пока не будет запущен BlockA . См. Проблему?

    В документации четко указано, что передача текущей очереди приведет к тупиковой ситуации.

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

    Эта проблема возникает, когда вы пытаетесь использовать GCD в качестве механизма взаимного исключения, и этот конкретный случай эквивалентен использованию рекурсивного мьютекса. Я не хочу вдаваться в аргумент о том, лучше ли использовать GCD или традиционный API взаимного исключения, например mutexs pthreads, или даже полезно использовать рекурсивные мьютексы; Я позволю другим спорить об этом, но есть определенная потребность в этом, особенно когда это главная очередь, с которой вы имеете дело.

    Лично я считаю, что dispatch_sync был бы более полезен, если бы он поддерживал это, или если была другая функция, обеспечивающая альтернативное поведение. Я призываю других, которые так считают, чтобы сообщить об ошибке с Apple (как я уже сделал, ID: 12668073).

    Вы можете написать свою собственную функцию, чтобы сделать то же самое, но это немного взломать:

     // Like dispatch_sync but works on current queue static inline void dispatch_synchronized (dispatch_queue_t queue, dispatch_block_t block) { dispatch_queue_set_specific (queue, queue, (void *)1, NULL); if (dispatch_get_specific (queue)) block (); else dispatch_sync (queue, block); } 

    NB Раньше у меня был пример, который использовал dispatch_get_current_queue (), но теперь он устарел.

    И dispatch_async и dispatch_sync выполняют толкание своего действия на нужную очередь. Действие не происходит немедленно; это происходит на какой-то будущей итерации цикла запуска очереди. Разница между dispatch_async и dispatch_sync заключается в том, что dispatch_sync блокирует текущую очередь, пока действие не завершится.

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

    Теперь вы можете спросить, когда асинхронно выполняете действие в текущей очереди, почему бы не просто просто вызвать функцию напрямую, а не ждать до некоторого будущего времени. Ответ заключается в том, что между ними существует большая разница. Много раз вам нужно выполнить действие, но оно должно выполняться после того, как любые побочные эффекты выполняются функциями вверху стека в текущей итерации цикла выполнения; или вам нужно выполнить свое действие после некоторого действия анимации, которое уже запланировано в цикле выполнения и т. д. Поэтому много раз вы увидите код [obj performSelector:selector withObject:foo afterDelay:0] (да, это другое из [obj performSelector:selector withObject:foo] ).

    Как мы уже говорили, dispatch_sync совпадает с dispatch_async , за исключением того, что он блокируется до завершения действия. Поэтому очевидно, почему это было бы тупиком – блок не может выполняться до тех пор, пока не будет завершена текущая итерация цикла запуска; но мы ждем его завершения, прежде чем продолжить.

    Теоретически можно было бы сделать особый случай для dispatch_sync когда он является текущим streamом, для его немедленного выполнения. (Такой особый случай существует для performSelector:onThread:withObject:waitUntilDone: когда stream является текущим streamом и waitUntilDone: ДА, он выполняет его немедленно.) Однако, я думаю, Apple решила, что лучше иметь последовательное поведение здесь независимо от очереди.

    Найдено из следующей документации. https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_sync

    В отличие от dispatch_async функция ” dispatch_sync ” не возвращается, пока блок не закончит. Вызов этой функции и таргетинг на текущую очередь приводит к взаимоблокировке.

    В отличие от dispatch_async , в целевой очереди не выполняется сохранение. Поскольку вызовы этой функции синхронны, она « заимствует » ссылку вызывающего. Кроме того, на блоке не выполняется Block_copy .

    В качестве оптимизации эта функция, когда это возможно, вызывает блок на текущем streamе.

    Interesting Posts

    Как создать резервную копию библиотеки iPhoto на внешний жесткий диск без использования Time Machine

    Как я узнаю, действительно ли встроенная функция заменена в том месте, где она вызывается или нет?

    К ARC или не к ARC? Каковы плюсы и минусы?

    pandas 0.21.0 Проблема совместимости с меткой времени с помощью matplotlib

    Что будет форматировать C: \ do?

    JTable, RowFilter и RowFilter.Entry

    Загрузка нескольких файлов без использования Zip-файла

    Установите новое аудиоустройство по умолчанию для громкоговорителя в Windows 10

    Сервер не может установить статус после отправки заголовков HTTP IIS7.5

    Есть ли способ получить большой рабочий стол, чем экран?

    Ресурсы изображений WPF

    Помимо скорости, как SSD сравниваются с жесткими дисками?

    Заставьте консоль ждать ввода пользователем, чтобы закрыть

    Есть ли способ сделать частичную загрузку AngularJS вначале, а не в случае необходимости?

    Пространство имен cstdio stdio.h

    Давайте будем гением компьютера.