ความแตกต่างระหว่าง dispatch_async และ dispatch_sync บน Serial Queue?


125

ฉันได้สร้างอนุกรมคิวดังนี้:

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

อะไรคือความแตกต่างระหว่างที่dispatch_asyncเรียกเช่นนี้

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

แล้วdispatch_syncเรียกแบบนี้บนซีเรียลคิว?

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

ความเข้าใจของฉันคือไม่ว่าจะใช้วิธีการจัดส่งแบบใดTASK 1จะถูกดำเนินการและเสร็จสิ้นก่อนTASK 2ถูกต้องหรือไม่?

คำตอบ:


410

ใช่. การใช้คิวอนุกรมช่วยให้มั่นใจได้ว่าการดำเนินการตามลำดับ ข้อแตกต่างเพียงอย่างเดียวคือdispatch_syncส่งคืนหลังจากบล็อกเสร็จสิ้นเท่านั้นในขณะที่dispatch_asyncส่งคืนหลังจากเพิ่มลงในคิวแล้วและอาจไม่เสร็จสิ้น

สำหรับรหัสนี้

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

มันอาจจะพิมพ์2413หรือ2143หรือ1234แต่1ก่อนเสมอ3

สำหรับรหัสนี้

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

มันมักจะพิมพ์ 1234


หมายเหตุ: สำหรับรหัสแรกก็จะไม่1324พิมพ์ เนื่องจากprintf("3")ถูกส่งไปหลังจาก printf("2")ดำเนินการแล้ว และงานสามารถดำเนินการได้หลังจากที่ส่งไปแล้วเท่านั้น


เวลาดำเนินการของงานไม่เปลี่ยนแปลงอะไร รหัสนี้จะพิมพ์เสมอ12

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

สิ่งที่อาจเกิดขึ้นคือ

  • เธรด 1: dispatch_async งานที่เสียเวลา (ภารกิจ 1) ไปยังคิวอนุกรม
  • เธรด 2: เริ่มดำเนินการภารกิจ 1
  • เธรด 1: dispatch_async งานอื่น (ภารกิจ 2) ไปยังคิวอนุกรม
  • เธรด 2: ภารกิจ 1 เสร็จสิ้น เริ่มดำเนินการภารกิจ 2
  • เธรด 2: ภารกิจ 2 เสร็จสิ้น

และคุณจะเห็นเสมอ 12


7
นอกจากนี้ยังสามารถพิมพ์ 2134 และ 1243
Matteo Gobbi

คำถามของฉันคือทำไมเราไม่ทำเหมือนวิธีปกติ? printf("1");printf("2") ;printf("3") ;printf("4")- เทียบกับdispatch_sync
androniennn

@androniennn สำหรับตัวอย่างที่สอง? เนื่องจากเธรดอื่น ๆ บางชุดอาจทำงานdispatch_sync(_serialQueue, ^{ /*change shared data*/ });ในเวลาเดียวกัน
Bryan Chen

1
@ asma22 การแชร์อ็อบเจ็กต์ที่ไม่ใช่เธรดปลอดภัยระหว่างเธรด / คิวการจัดส่งหลายรายการมีประโยชน์มาก หากคุณเข้าถึงวัตถุในคิวอนุกรมเท่านั้นคุณจะรู้ว่าคุณกำลังเข้าถึงวัตถุนั้นอย่างปลอดภัย
Bryan Chen

1
ฉันจะหมายถึงการดำเนินการแบบอนุกรม ในมุมมองที่ว่างานทั้งหมดถูกดำเนินการแบบอนุกรมเกี่ยวกับงานอื่น ๆ ในคิวเดียวกัน สาเหตุก็ยังสามารถเกี่ยวข้องกับคิวอื่น ๆ พร้อมกันได้ เป็นจุดรวมของ GCD ที่สามารถส่งงานและดำเนินการไปพร้อมกันได้
Bryan Chen

19

ความแตกต่างระหว่างdispatch_syncและdispatch_asyncเป็นเรื่องง่าย

ในทั้งสองตัวอย่างของคุณTASK 1จะดำเนินการก่อนเสมอTASK 2เนื่องจากถูกส่งไปก่อนหน้านั้น

อย่างไรก็ตามในdispatch_syncตัวอย่างนี้คุณจะไม่จัดส่งTASK 2จนกว่าจะTASK 1ถูกส่งและดำเนินการในภายหลัง นี้เรียกว่า"ปิดกั้น" รหัสของคุณจะรอ (หรือ "บล็อก") จนกว่างานจะดำเนินการ

ในdispatch_asyncตัวอย่างโค้ดของคุณจะไม่รอให้การดำเนินการเสร็จสมบูรณ์ บล็อกทั้งสองจะส่ง (และจัดคิว) ไปยังคิวและโค้ดที่เหลือของคุณจะดำเนินการต่อในเธรดนั้น จากนั้นในอนาคต (ขึ้นอยู่กับสิ่งที่ถูกส่งไปยังคิวของคุณ) Task 1จะดำเนินการจากนั้นจึงTask 2จะดำเนินการ


2
ฉันคิดว่าคำสั่งซื้อของคุณผิด ตัวอย่างแรกคือasyncเวอร์ชันที่ไม่ปิดกั้น
Bryan Chen

ฉันได้แก้ไขคำตอบของสิ่งที่ฉันคิดว่าคุณหมายถึง หากไม่เป็นเช่นนั้นโปรดเปลี่ยนและชี้แจง
JRG-Developer

1
จะเกิดอะไรขึ้นถ้าคุณเรียก dispatch_sync แล้ว dispatch_async ในคิวเดียวกัน (และในทางกลับกัน)
0xSina

1
ในคิวอนุกรมงานทั้งสองยังคงดำเนินการทีละงาน ในกรณีแรกผู้โทรจะรอให้บล็อกแรกเสร็จสิ้น แต่ไม่รอให้บล็อกที่สอง ในกรณีที่สองผู้โทรไม่รอให้บล็อกแรกเสร็จสิ้น แต่รอให้บล็อกที่สอง แต่เนื่องจากคิวดำเนินการบล็อกตามลำดับผู้โทรจึงรอให้ทั้งคู่เสร็จสิ้น
gnasher729

1
บล็อกสามารถทำ dispatch_async ในคิวของตัวเองได้ (เพิ่มบล็อกเพิ่มเติมซึ่งจะดำเนินการในภายหลัง) dispatch_sync บนคิวอนุกรมหรือคิวหลักของตัวเองจะชะงักงัน ในสถานการณ์นี้ผู้โทรจะรอให้บล็อกเดิมเสร็จสิ้น แต่ไม่ใช่สำหรับบล็อกอื่น โปรดจำไว้ว่า: dispatch_sync วางบล็อกไว้ที่ท้ายคิวคิวจะรันโค้ดจนกว่าบล็อกนั้นจะเสร็จสิ้นจากนั้นจึงส่งคืน dispatch_sync dispatch_async เพิ่มบล็อกที่ท้ายคิว
gnasher729

6

ทั้งหมดเกี่ยวข้องกับคิวหลัก มี 4 การเรียงสับเปลี่ยน

i) อนุกรมคิวส่ง async: ที่นี่งานจะดำเนินการทีละรายการ แต่เธรดหลัก (ผลต่อ UI) จะไม่รอการส่งคืน

ii) คิวอนุกรมการซิงค์การจัดส่ง: ที่นี่งานจะดำเนินการทีละรายการ แต่เธรดหลัก (ผลต่อ UI) จะแสดงความล่าช้า

iii) คิวพร้อมกันส่ง async: ที่นี่งานจะดำเนินการแบบคู่ขนานและเธรดหลัก (ผลต่อ UI) จะไม่รอการส่งคืนและจะราบรื่น

iv) คิวพร้อมกันการซิงค์การจัดส่ง: ที่นี่งานจะดำเนินการแบบขนาน แต่เธรดหลัก (ผลต่อ UI) จะแสดงความล่าช้า

การเลือกคิวพร้อมกันหรือคิวอนุกรมขึ้นอยู่กับว่าคุณต้องการเอาต์พุตจากงานก่อนหน้าสำหรับงานถัดไปหรือไม่ หากคุณขึ้นอยู่กับงานก่อนหน้าให้นำคิวอนุกรมอื่นมาใช้คิวพร้อมกัน

และสุดท้ายนี้เป็นวิธีเจาะกลับไปที่เธรดหลักเมื่อเราทำธุรกิจของเราเสร็จแล้ว:

DispatchQueue.main.async {
     // Do something here
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.