Różnica między dispatch_async i dispatch_sync w kolejce szeregowej?

125

Utworzyłem kolejkę szeregową w ten sposób:

    dispatch_queue_t _serialQueue = dispatch_queue_create("com.example.name", DISPATCH_QUEUE_SERIAL);

Jaka jest różnica między dispatch_asyncnazywane tak

 dispatch_async(_serialQueue, ^{ /* TASK 1 */ });
 dispatch_async(_serialQueue, ^{ /* TASK 2 */ });

I dispatch_syncdzwoniłeś w ten sposób w tej kolejce szeregowej?

 dispatch_sync(_serialQueue, ^{ /* TASK 1 */ });
 dispatch_sync(_serialQueue, ^{ /* TASK 2 */ });

Rozumiem, że niezależnie od zastosowanej metody wysyłki TASK 1zostanie wykonana i zakończona przed TASK 2, prawda?

Programista JRG
źródło

Odpowiedzi:

409

Tak. Korzystanie z kolejki szeregowej zapewnia seryjne wykonywanie zadań. Jedyną różnicą jest to, że dispatch_syncpowrót następuje tylko po zakończeniu bloku, podczas gdy dispatch_asyncpowrót po dodaniu go do kolejki i może się nie zakończyć.

dla tego kodu

dispatch_async(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_async(_serialQueue, ^{ printf("3"); });
printf("4");

To może wydrukować 2413lub 2143czy 1234jednak 1zawsze przed3

dla tego kodu

dispatch_sync(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_sync(_serialQueue, ^{ printf("3"); });
printf("4");

zawsze drukuje 1234


Uwaga: w przypadku pierwszego kodu nie zostanie wydrukowany 1324. Ponieważ printf("3")jest wysyłany po printf("2") wykonaniu. Zadanie można wykonać dopiero po wysłaniu.


Czas wykonania zadań niczego nie zmienia. Ten kod jest zawsze drukowany12

dispatch_async(_serialQueue, ^{ sleep(1000);printf("1"); });
dispatch_async(_serialQueue, ^{ printf("2"); });

To, co może się wydarzyć, jest

  • Wątek 1: dispatch_async czasochłonne zadanie (zadanie 1) do kolejki szeregowej
  • Wątek 2: Rozpocznij wykonywanie zadania 1
  • Wątek 1: Dispatch_async inne zadanie (zadanie 2) do kolejki szeregowej
  • Wątek 2: zadanie 1 zakończone. rozpocznij wykonywanie zadania 2
  • Wątek 2: zadanie 2 zakończone.

i zawsze widzisz 12

Bryan Chen
źródło
7
może również wydrukować 2134 i 1243
Matteo Gobbi
moje pytanie brzmi: dlaczego nie zrobiliśmy tego w normalny sposób? printf("1");printf("2") ;printf("3") ;printf("4")- w porównaniu zdispatch_sync
androniennn
@androniennn jako drugi przykład? ponieważ dispatch_sync(_serialQueue, ^{ /*change shared data*/ });w tym samym czasie może działać inny wątek .
Bryan Chen,
1
@ asma22 Bardzo przydatne jest udostępnianie bezpiecznego obiektu niebędącego wątkiem między wieloma wątkami / kolejkami wysyłkowymi. Jeśli uzyskujesz dostęp do obiektu tylko w kolejce szeregowej, wiesz, że uzyskujesz do niego bezpieczny dostęp.
Bryan Chen
1
Mam na myśli seryjną egzekucję . Z punktu widzenia, że ​​wszystkie zadania są wykonywane seryjnie w odniesieniu do innych zadań w tej samej kolejce. Oczywiście może to być równoczesne w odniesieniu do innych kolejek. Głównym celem GCD jest to, że zadania mogą być wysyłane i wykonywane jednocześnie.
Bryan Chen
19

Różnica między dispatch_synci dispatch_asyncjest prosta.

W obu twoich przykładach TASK 1zawsze będzie wykonywany wcześniej, TASK 2ponieważ został wysłany przed nim.

W tym dispatch_syncprzykładzie nie wyślesz jednak, TASK 2dopóki TASK 1nie zostanie wysłany i wykonany . Nazywa się to „blokowaniem” . Twój kod czeka (lub „blokuje”), aż zadanie zostanie wykonane.

W tym dispatch_asyncprzykładzie Twój kod nie będzie czekał na zakończenie wykonywania. Oba bloki zostaną wysłane (i umieszczone w kolejce) do kolejki, a reszta kodu będzie kontynuowana w tym wątku. Następnie w pewnym momencie w przyszłości (w zależności od tego, co jeszcze zostało wysłane do Twojej kolejki),Task 1 wykona, a następnie Task 2wykona.

Dave DeLong
źródło
2
Myślę, że źle zamówiłeś. pierwszy przykład to asyncwersja nieblokująca
Bryan Chen,
Zmieniłem twoją odpowiedź na to, co myślę, że miałeś na myśli . Jeśli tak nie jest, zmień to i wyjaśnij.
JRG-Developer,
1
Co się stanie, jeśli wywołasz dispatch_sync, a następnie dispatch_async w tej samej kolejce? (i odwrotnie)
0xSina
1
W kolejce szeregowej oba zadania są nadal wykonywane jedno po drugim. W pierwszym przypadku dzwoniący czeka na zakończenie pierwszego bloku, ale nie czeka na drugi blok. W drugim przypadku dzwoniący nie czeka na zakończenie pierwszego bloku, ale czeka na drugi blok. Ale ponieważ kolejka wykonuje bloki po kolei, wywołujący faktycznie czeka na zakończenie obu.
gnasher729
1
Blok mógłby również wykonać procedurę dispatch_async na swojej własnej kolejce (dodając kolejne bloki, które zostaną wykonane później); dispatch_sync we własnej kolejce szeregowej lub kolejce głównej byłby zakleszczony. W tej sytuacji dzwoniący będzie czekał na zakończenie oryginalnego bloku, ale nie na inne bloki. Pamiętaj tylko: dispatch_sync umieszcza blok na końcu kolejki, kolejka wykonuje kod do zakończenia tego bloku, a następnie powraca dispatch_sync. dispatch_async po prostu dodaje blok na końcu kolejki.
gnasher729
5

To wszystko jest związane z główną kolejką. Istnieją 4 permutacje.

i) Kolejka szeregowa, wysyłka asynchroniczna: tutaj zadania będą wykonywane jedno po drugim, ale główny wątek (wpływ na interfejs użytkownika) nie będzie czekał na powrót

ii) Kolejka szeregowa, synchronizacja wysyłki: Tutaj zadania będą wykonywane jedno po drugim, ale główny wątek (wpływ na interfejs użytkownika) pokaże opóźnienie

iii) Kolejka współbieżna, wysyłka asynchroniczna: tutaj zadania będą wykonywane równolegle, a główny wątek (wpływ na interfejs użytkownika) nie będzie czekał na powrót i będzie działał płynnie.

iv) Równoczesna kolejka, synchronizacja wysyłek: tutaj zadania będą wykonywane równolegle, ale główny wątek (wpływ na interfejs użytkownika) pokaże opóźnienie

Wybór kolejki współbieżnej lub szeregowej zależy od tego, czy potrzebujesz danych wyjściowych z poprzedniego zadania dla następnego. Jeśli polegasz na poprzednim zadaniu, zastosuj kolejkę szeregową, w przeciwnym razie weź kolejkę współbieżną.

I na koniec, jest to sposób na powrót do głównego wątku, gdy skończymy z naszą działalnością:

DispatchQueue.main.async {
     // Do something here
}
r & D_
źródło