iPhone - główny wątek Grand Central Dispatch

145

Z powodzeniem korzystałem z funkcji Grand Central Dispatch w moich aplikacjach, ale zastanawiałem się, jaka jest prawdziwa zaleta korzystania z czegoś takiego:

dispatch_async(dispatch_get_main_queue(), ^{ ... do stuff

lub nawet

dispatch_sync(dispatch_get_main_queue(), ^{ ... do stuff

Chodzi mi o to, że w obu przypadkach odpalasz blok do wykonania w głównym wątku, dokładnie tam, gdzie działa aplikacja, a to nie pomoże zmniejszyć obciążenia. W pierwszym przypadku nie masz żadnej kontroli, kiedy blok zostanie uruchomiony. Widziałem przypadki wykonywania bloków pół sekundy po ich wystrzeleniu. W drugim przypadku jest podobny do

[self doStuff];

dobrze?

Ciekawe, co o tym myślicie.

kaczka
źródło
9
Nawiasem mówiąc, wrzucenie głównej kolejki do dispatch_sync spowoduje zakleszczenie.
Brooks Hanes,
5
Po prostu przeczytaj to w dokumentach: „W przeciwieństwie do dispatch_async, [dispatch_sync] nie zwraca, dopóki blok się nie zakończy. Wywołanie tej funkcji i wskazanie bieżącej kolejki powoduje zakleszczenie.” ... Ale być może źle to czytam ... ( aktualna kolejka nie oznacza głównego wątku). Popraw, jeśli się mylę.
Brooks Hanes
4
@BrooksHanes nie zawsze jest prawdą. Spowoduje to zakleszczenie, jeśli jesteś już w głównym wątku. Jeśli nie, to nie byłoby impasu. Zobacz tutaj
Honey

Odpowiedzi:

296

Wysłanie bloku do kolejki głównej odbywa się zwykle z kolejki w tle, aby zasygnalizować, że pewne przetwarzanie w tle zostało zakończone, np

- (void)doCalculation
{
    //you can use any string instead "com.mycompany.myqueue"
    dispatch_queue_t backgroundQueue = dispatch_queue_create("com.mycompany.myqueue", 0);

    dispatch_async(backgroundQueue, ^{
        int result = <some really long calculation that takes seconds to complete>;

        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateMyUIWithResult:result];
        });    
    });
}

W tym przypadku wykonujemy długie obliczenia w kolejce w tle i musimy zaktualizować nasz interfejs użytkownika po zakończeniu obliczeń. Aktualizacja interfejsu użytkownika zwykle musi być wykonywana z głównej kolejki, więc „sygnalizujemy” z powrotem do głównej kolejki za pomocą drugiego zagnieżdżonego dispatch_async.

Prawdopodobnie istnieją inne przykłady, w których możesz chcieć wysłać z powrotem do głównej kolejki, ale zazwyczaj odbywa się to w ten sposób, tj. Zagnieżdżone z bloku wysyłanego do kolejki w tle.

  • Przetwarzanie w tle zakończone -> zaktualizuj interfejs użytkownika
  • porcja danych przetwarzanych w kolejce w tle -> sygnalizuj głównej kolejce, aby rozpocząć kolejną porcję
  • przychodzące dane sieciowe w kolejce w tle -> sygnalizuj głównej kolejce, że wiadomość dotarła
  • itd itd

Co do tego, dlaczego możesz chcieć wysłać wiadomość do głównej kolejki z głównej kolejki ... Cóż, generalnie byś tego nie robił, chociaż niewykluczone, że możesz to zrobić, aby zaplanować trochę pracy do wykonania następnym razem wokół pętli uruchamiania.

Robin Summerhill
źródło
O, rozumiem. Więc mam rację. Nie ma z tego żadnej korzyści, jeśli jesteś już w głównej kolejce, tylko jeśli jesteś w innej kolejce i chcesz zaktualizować interfejs użytkownika. Dzięki.
Duck
Właśnie zredagowałem moją odpowiedź, aby powiedzieć, dlaczego nie jest to zbyt przydatne, aby zrobić to z głównej kolejki.
Robin Summerhill
Myślę też, że w iOS 4 jest błąd (mógł zniknąć w iOS 5), gdzie dispatch_sync do głównej kolejki z głównego wątku powoduje zawieszenie, więc unikałbym tego całkowicie.
joerick
10
To nie jest błąd, to oczekiwane zachowanie. Wprawdzie niezbyt przydatne zachowanie, ale zawsze musisz być świadomy zakleszczeń podczas korzystania z dispatch_sync. Nie możesz oczekiwać, że system będzie chronił Cię przez cały czas przed błędami programisty.
Robin Summerhill
2
Co to jest backgroundQueue? Jak utworzyć obiekt backgroundQueue
Nilesh Tupe
16

Wysyłanie bloków do głównej kolejki z głównego wątku może być przydatne. Daje głównej kolejce szansę na obsłużenie innych bloków, które zostały umieszczone w kolejce, abyś nie blokował wykonywania wszystkiego innego.

Na przykład możesz napisać zasadniczo jednowątkowy serwer, który mimo to obsługuje wiele jednoczesnych połączeń. Dopóki żaden pojedynczy blok w kolejce nie zajmuje zbyt dużo czasu, serwer reaguje na nowe żądania.

Jeśli twój program nie robi nic, tylko spędza całe życie odpowiadając na zdarzenia, może to być całkiem naturalne. Po prostu skonfigurowałeś swoje programy obsługi zdarzeń tak, aby działały w głównej kolejce, a następnie wywołujesz metodę dispatch_main () i możesz w ogóle nie martwić się o bezpieczeństwo wątków.

bames53
źródło
11

Mam nadzieję, że dobrze rozumiem Twoje pytanie, ponieważ zastanawiasz się nad różnicami między dispatch_async i dispatch_sync?

dispatch_async

wyśle ​​blok do kolejki asynchronicznie. Oznacza to, że wyśle ​​blok do kolejki i nie będzie czekał na jego powrót przed kontynuowaniem wykonywania pozostałego kodu w Twojej metodzie.

dispatch_sync

wyśle ​​blok do kolejki synchronicznie. Zapobiegnie to dalszemu wykonywaniu pozostałego kodu w metodzie do momentu zakończenia wykonywania bloku.

Najczęściej używałem dispatch_asyncdo kolejki w tle, aby usunąć pracę z głównej kolejki i wykorzystać wszelkie dodatkowe rdzenie, które może mieć urządzenie. Następniedispatch_async do głównego wątku, jeśli muszę zaktualizować interfejs użytkownika.

Powodzenia

timthetoolman
źródło
1
dzięki, ale pytam o korzyści płynące z wysłania czegoś do głównej kolejki, będąc w głównej kolejce.
Duck
9

Jedno miejsce, w którym jest to przydatne, to czynności związane z interfejsem użytkownika, takie jak ustawienie pokrętła przed długą operacją:

- (void) handleDoSomethingButton{

    [mySpinner startAnimating];

    (do something lengthy)
    [mySpinner stopAnimating];
}

nie zadziała, ponieważ blokujesz główny wątek podczas swojej długiej rzeczy i nie pozwalasz UIKit faktycznie uruchomić przędzarki.

- (void) handleDoSomethingButton{
     [mySpinner startAnimating];

     dispatch_async (dispatch_get_main_queue(), ^{
          (do something lengthy)
          [mySpinner stopAnimating];
    });
}

zwróci sterowanie do pętli uruchamiania, która zaplanuje aktualizację interfejsu użytkownika, uruchomienie pokrętła, a następnie usunie następną rzecz z kolejki wysyłania, czyli rzeczywiste przetwarzanie. Po zakończeniu przetwarzania wywoływane jest zatrzymanie animacji i powracasz do pętli uruchamiania, gdzie interfejs użytkownika jest aktualizowany po zatrzymaniu.

weaselfloss1
źródło
@Jerceratops tak, ale pozwala na zakończenie bieżącego runloop.
Dan Rosenstark
3
Tak, ale to wciąż straszne. Nadal blokuje interfejs użytkownika. Mógłbym wcisnąć inny przycisk zaraz po tym. Lub spróbuj i przewiń. „(zrób coś długiego)” nie powinno się zdarzyć w głównym wątku, a dispatch_async pozwalający na kliknięcie przycisku „zakończ” nie jest akceptowalnym rozwiązaniem.
Jerceratops,
8

Swift 3, 4 i 5

Uruchamianie kodu w głównym wątku

DispatchQueue.main.async {
    // Your code here
}
Niall Kiddle
źródło
1

Async oznacza asynchroniczność i powinieneś jej używać przez większość czasu. Nigdy nie powinieneś wywoływać synchronizacji w głównym wątku, ponieważ spowoduje to zablokowanie interfejsu użytkownika do czasu zakończenia zadania. You Here to lepszy sposób na zrobienie tego w Swift:

runThisInMainThread { () -> Void in
    // Run your code like this:
    self.doStuff()
}

func runThisInMainThread(block: dispatch_block_t) {
    dispatch_async(dispatch_get_main_queue(), block)
}

Jest zawarta jako standardowa funkcja w moim repozytorium, sprawdź: https://github.com/goktugyil/EZSwiftExtensions

Esqarrouth
źródło