Perbedaan antara dispatch_async dan dispatch_sync pada antrian serial?

125

Saya telah membuat antrian serial seperti ini:

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

Apa bedanya dispatch_asyncdisebut seperti ini

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

Dan dispatch_syncdipanggil seperti ini di antrian serial ini?

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

Pemahaman saya adalah bahwa, terlepas dari metode pengiriman mana yang digunakan, TASK 1akan dieksekusi dan diselesaikan sebelumnya TASK 2, benar?

JRG-Pengembang
sumber

Jawaban:

409

Iya. Menggunakan antrian serial memastikan eksekusi serial tugas. Satu-satunya perbedaan adalah bahwa dispatch_synchanya kembali setelah blok selesai sedangkan dispatch_asynckembali setelah ditambahkan ke antrian dan mungkin tidak selesai.

untuk kode ini

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

Mungkin mencetak 2413atau 2143atau 1234tetapi 1selalu sebelumnya3

untuk kode ini

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

selalu dicetak 1234


Catatan: Untuk kode pertama, tidak akan dicetak 1324. Karena printf("3")dikirim setelah printf("2") dieksekusi. Dan tugas hanya dapat dieksekusi setelah dikirim.


Waktu pelaksanaan tugas tidak mengubah apa pun. Kode ini selalu dicetak12

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

Apa yang mungkin terjadi adalah

  • Thread 1: dispatch_async tugas yang memakan waktu (tugas 1) ke antrian serial
  • Thread 2: mulai menjalankan tugas 1
  • Thread 1: dispatch_async tugas lain (tugas 2) ke antrian serial
  • Thread 2: tugas 1 selesai. mulai menjalankan tugas 2
  • Thread 2: tugas 2 selesai.

dan kamu selalu melihat 12

Bryan Chen
sumber
7
itu juga dapat mencetak 2134 dan 1243
Matteo Gobbi
pertanyaan saya adalah mengapa kita tidak melakukannya dengan cara biasa? printf("1");printf("2") ;printf("3") ;printf("4")- dibandingkan dengandispatch_sync
androniennn
@ Androniennn untuk contoh kedua? karena beberapa utas lainnya dapat berjalan dispatch_sync(_serialQueue, ^{ /*change shared data*/ });pada saat yang sama.
Bryan Chen
1
@ asma22 Sangatlah berguna untuk berbagi objek aman non-utas antara beberapa utas / antrian pengiriman. Jika Anda hanya mengakses objek dalam antrian serial, Anda tahu Anda mengaksesnya dengan aman.
Bryan Chen
1
Maksud saya eksekusi serial . Dalam pandangan bahwa semua tugas dieksekusi secara serial berkaitan dengan tugas-tugas lain dalam antrian yang sama. Penyebabnya masih bisa bersamaan salam dengan antrian lainnya. Ini adalah inti dari GCD bahwa tugas dapat dikirim dan dieksekusi bersamaan.
Bryan Chen
19

Perbedaan antara dispatch_syncdan dispatch_asyncsederhana.

Dalam kedua contoh Anda, TASK 1akan selalu dieksekusi sebelumnya TASK 2karena telah dikirim sebelumnya.

dispatch_syncNamun, dalam contoh, Anda tidak akan mengirim TASK 2sampai setelah TASK 1dikirim dan dieksekusi . Ini disebut "pemblokiran" . Kode Anda menunggu (atau "memblokir") sampai tugas dijalankan.

Dalam dispatch_asynccontoh ini, kode Anda tidak akan menunggu eksekusi selesai. Kedua blok akan mengirimkan (dan akan di-enqueued) ke antrian dan sisa kode Anda akan terus dieksekusi pada utas itu. Kemudian di beberapa titik di masa depan, (tergantung pada apa lagi yang telah dikirim ke antrian Anda), Task 1akan mengeksekusi dan kemudian Task 2akan mengeksekusi.

Dave DeLong
sumber
2
Saya pikir pesanan Anda salah. Contoh pertama adalah asyncyang merupakan versi non-pemblokiran
Bryan Chen
Saya telah mengedit jawaban Anda untuk apa yang saya pikir Anda maksudkan . Jika ini bukan masalahnya, harap ubah dan klarifikasi.
JRG-Pengembang
1
Bagaimana jika Anda memanggil dispatch_sync dan kemudian dispatch_async pada antrian yang sama? (dan sebaliknya)
0xSina
1
Pada antrian serial, kedua tugas masih dieksekusi satu demi satu. Dalam kasus pertama, pemanggil menunggu blok pertama selesai tetapi tidak menunggu blok kedua. Dalam kasus kedua, penelepon tidak menunggu sampai blok pertama selesai, tetapi menunggu blok kedua. Tetapi karena antrian mengeksekusi blok secara berurutan, penelepon secara efektif menunggu keduanya selesai.
gnasher729
1
Blok A juga bisa melakukan dispatch_async pada antriannya sendiri (menambahkan blok selanjutnya yang akan dieksekusi nanti); dispatch_sync pada antrian serial sendiri atau antrian utama akan menemui jalan buntu. Dalam situasi ini, penelepon akan menunggu blok asli selesai, tetapi tidak untuk blok lainnya. Ingat saja: dispatch_sync menempatkan blok di akhir antrian, antrian mengeksekusi kode sampai blok itu selesai, dan kemudian dispatch_sync kembali. dispatch_async baru saja menambahkan blokir di akhir antrian.
gnasher729
5

Itu semua terkait dengan antrian utama. Ada 4 permutasi.

i) Serial queue, dispatch async: Di sini tugas akan dieksekusi satu demi satu, tetapi utas utama (efek pada UI) tidak akan menunggu untuk kembali

ii) Antrian serial, sinkronisasi pengiriman: Di sini tugas akan dieksekusi satu demi satu, tetapi utas utama (efek pada UI) akan menunjukkan keterlambatan

iii) Antrian bersamaan, kirim async: Di sini tugas akan dieksekusi secara paralel dan utas utama (efek pada UI) tidak akan menunggu untuk kembali dan akan lancar.

iv) Antrian bersamaan, pengiriman sinkronisasi: Di ​​sini tugas akan dieksekusi secara paralel, tetapi utas utama (efek pada UI) akan menunjukkan kelambatan

Pilihan Anda untuk antrian serentak atau serial bergantung pada apakah Anda memerlukan output dari tugas sebelumnya untuk tugas berikutnya. Jika Anda bergantung pada tugas sebelumnya, gunakan antrian serial jika tidak, ambil antrian bersamaan.

Dan terakhir ini adalah cara untuk menembus kembali ke utas utama ketika kita selesai dengan bisnis kami:

DispatchQueue.main.async {
     // Do something here
}
rd_
sumber