26

Używam szkieletu testowego Kiwi do testowania metody uwierzytelniania w mojej aplikacji. Zawiesza testy na wezwanie do dispatch_sync który wygląda następująco:Dlaczego to wywołanie funkcji dispatch_sync() jest blokowane?

dispatch_queue_t main = dispatch_get_main_queue(); 
dispatch_sync(main,^
        { 
         [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationAuthenticationSuccess object:nil userInfo:ret]; 
        }); 

Chciałbym wiedzieć, dlaczego zamarza tam, jeśli ktoś ma jakieś wskazówki.

+0

To deadlocking bo jesteś enqueuing blok na głównej kolejki - co jest w głównym wątku - i mówi główny wątek czekać na które blok do ukończenia. –

+0

Ponieważ odpowiedź, którą zaakceptowałeś, wcale nie rozwiązuje problemu, o który pytałeś, usunąłem tę część z pytania. Pytanie o zakleszczenie zostało już wielokrotnie potwierdzone (i jest również w dokumentach): http://stackoverflow.com/questions/7816159/dispatch-sync-on-main-queue-hangs-in-unit-test, http : //stackoverflow.com/questions/10315645/app-blocks-while-dipatching-a-queue, http://stackoverflow.com/questions/10984732/gcd-why-cant-we-use-a-dispatch-sync -do-bieżącej-kolejki, ale myślałem, że pytanie o odgadnięcie funkcji GCD było całkiem interesujące. –

+0

Dzięki.Gdybym wiedział, że to był problem z zakleszczeniem, mógłbym odpowiednio dostosować pytanie lub znaleźć rozwiązanie, wyszukując google. Byłoby miło wiedzieć, jak wywołać dispatch_sync, ale może nie jest to właściwe rozwiązanie. – teubanks

Odpowiedz

50

Dla drugiej części pytania dotyczącej nutą sprawie zamrożenia:

Dzwoniąc dispatch_sync w kolejce, zawsze upewnić się, że ta kolejka nie jest już aktualna kolejka (dispatch_get_current_queue()). Ponieważ spowoduje umieszczenie twojego bloku w kolejce w kolejce przekazanej jako pierwszy parametr, a następnie będzie oczekiwał na wykonanie tego bloku przed kontynuowaniem.

Jeśli więc dispatch_get_current_queue() i kolejka, w której umieściliśmy blok, są takie same, a mianowicie główna kolejka w twoim przypadku, główna kolejka zablokuje wywołanie funkcji dispatch_sync do ... głównej kolejki w trakcie wykonywania bloku, ale nie może, ponieważ kolejka jest zablokowana, i masz piękny zakleszczenie tutaj.

Jednym z rozwiązań ([EDIT] aż iOS6):

dispatch_queue_t main = dispatch_get_main_queue(); 
dispatch_block_t block =^
       { 
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationAuthenticationSuccess object:nil userInfo:ret]; 
       }; 
if (dispatch_get_current_queue() == main) 
    block(); // execute the block directly, as main is already the current active queue 
else 
    dispatch_sync(main, block); // ask the main queue, which is not the current queue, to execute the block, and wait for it to be executed before continuing 

[EDIT] Bądź ostrożny, dispatch_get_current_queue() jest używane tylko do celów debugowania i nigdy w produkcji. W rzeczywistości dispatch_get_current_queue jest przestarzałe od iOS6.1.3.

Jeśli jesteś w szczególnym przypadku głównej kolejki (która jest związana tylko z głównym wątkiem), możesz zamiast tego przetestować [NSThread isMainThread] zgodnie z sugestią @ meaning-matters.


Nawiasem mówiąc, to na pewno trzeba dispatch_sync w Twoim przypadku? Przypuszczam, że wysłanie powiadomienia nieco później, unikanie blokowania, dopóki nie zostanie wysłane, jest dopuszczalne w twoim przypadku, więc możesz również rozważyć użycie dispatch_async (zamiast używać dispatch_sync i wymagającego warunku porównywania kolejki), co pozwoli uniknąć impasu problem też.

+0

To jest fantastyczna odpowiedź. Powodem, dla którego używamy dispatch_sync jest to, że jest wywoływana w wywołaniu dispatch_async (nie ma pewności, czy to ma sens). Innymi słowy, jest to jedna z części synchronicznego procesu, który zachodzi w asynchronicznym wątku. – teubanks

+1

Należy pamiętać, że jeśli używasz kolejek wysyłki do serializowania dostępu do zasobu udostępnionego, natychmiastowe wykonanie bloku może czasami prowadzić do subtelnych błędów, ponieważ blok dispatch_sync zostanie wykonany przed blokami dispatch_async, które będą atakować tę samą kolejkę, nawet jeśli blok async został złożony jako pierwszy. –

+1

To rozwiązanie nie działa w systemie iOS 6.1.3 ([zobacz moją odpowiedź poniżej] (http://stackoverflow.com/a/15692778/1971013)). Natomiast 'dispatch_get_current_queue()' jest przestarzałe. –

36

dispatch_get_current_queue() jest przestarzała wychodząc z iOS 6 i dispatch_get_current_queue() == dispatch_get_main_queue() wynosiła false czasu na głównym gwintem iOS 6.1.3.

W iOS 6 i dalej po prostu zrobić:

dispatch_block_t block =^
{ 
    <your code here> 
}; 

if ([NSThread isMainThread]) 
{ 
    block(); 
} 
else 
{ 
    dispatch_sync(dispatch_get_main_queue(), block); 
} 
+1

niesamowite. to zadziałało dla mnie !! – vikas

+2

Nadal działa na iOS 8.2. – Naeem

+0

@ znaczenie-ważne dzięki za wsparcie ;-) – Naeem