Sự khác biệt giữa Clark_async và Clark_sync trên hàng đợi nối tiếp?


125

Tôi đã tạo một hàng đợi nối tiếp như thế này:

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

Sự khác biệt giữa dispatch_asyncđược gọi như thế này là gì

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

dispatch_syncđược gọi như thế này trên hàng đợi nối tiếp này?

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

Hiểu biết của tôi là, bất kể phương thức công văn nào được sử dụng, TASK 1sẽ được thực hiện và hoàn thành trước đó TASK 2, đúng không?

Câu trả lời:


409

Đúng. Sử dụng hàng đợi nối tiếp đảm bảo thực hiện nối tiếp các nhiệm vụ. Sự khác biệt duy nhất là dispatch_syncchỉ trả về sau khi khối kết thúc trong khi dispatch_asynctrả về sau khi được thêm vào hàng đợi và có thể không kết thúc.

cho mã này

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

Nó có thể in 2413hoặc 2143hay 1234nhưng 1luôn luôn trước3

cho mã này

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

nó luôn luôn in 1234


Lưu ý: Đối với mã đầu tiên, nó sẽ không in 1324. Bởi vì printf("3")được gửi sau khi printf("2") được thực thi. Và một nhiệm vụ chỉ có thể được thực hiện sau khi nó được gửi đi.


Thời gian thực hiện của các nhiệm vụ không thay đổi bất cứ điều gì. Mã này luôn luôn in12

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

Những gì có thể xảy ra là

  • Chủ đề 1: Clark_async một nhiệm vụ tốn thời gian (nhiệm vụ 1) cho hàng đợi nối tiếp
  • Chủ đề 2: bắt đầu thực hiện nhiệm vụ 1
  • Chủ đề 1: Clark_async một nhiệm vụ khác (nhiệm vụ 2) cho hàng đợi nối tiếp
  • Chủ đề 2: nhiệm vụ 1 đã hoàn thành. bắt đầu thực hiện nhiệm vụ 2
  • Chủ đề 2: nhiệm vụ 2 đã hoàn thành.

và bạn luôn thấy 12


7
nó cũng có thể in 2134 và 1243
Matteo Gobbi

Câu hỏi của tôi là tại sao chúng ta không làm điều đó như cách thông thường? printf("1");printf("2") ;printf("3") ;printf("4")- so vớidispatch_sync
androniennn

@androniennn cho ví dụ thứ hai? bởi vì một số chủ đề khác có thể chạy dispatch_sync(_serialQueue, ^{ /*change shared data*/ });cùng một lúc.
Bryan Chen

1
@ asma22 Rất hữu ích khi chia sẻ một đối tượng an toàn không có luồng giữa nhiều luồng / hàng đợi. Nếu bạn chỉ truy cập đối tượng trong hàng đợi nối tiếp, bạn biết bạn đang truy cập nó một cách an toàn.
Bryan Chen

1
Tôi có nghĩa là thực hiện nối tiếp . Theo quan điểm rằng tất cả các nhiệm vụ được thực hiện nối tiếp liên quan đến các nhiệm vụ khác trong cùng một hàng đợi. Bởi vì nó vẫn có thể đồng thời liên quan đến các hàng đợi khác. Đó là toàn bộ quan điểm của GCD rằng các nhiệm vụ có thể được gửi và thực hiện đồng thời.
Bryan Chen

19

Sự khác biệt giữa dispatch_syncdispatch_asynclà đơn giản.

Trong cả hai ví dụ của bạn, TASK 1sẽ luôn luôn thực thi trước TASK 2vì nó được gửi trước nó.

Trong dispatch_syncví dụ, tuy nhiên, bạn sẽ không gửi TASK 2cho đến khi TASK 1đã được gửi đi và được thực thi . Điều này được gọi là "chặn" . Mã của bạn chờ (hoặc "khối") cho đến khi tác vụ thực thi.

Trong dispatch_asyncví dụ này, mã của bạn sẽ không chờ thực thi hoàn tất. Cả hai khối sẽ gửi (và được xử lý) đến hàng đợi và phần còn lại của mã của bạn sẽ tiếp tục thực thi trên luồng đó. Sau đó, tại một thời điểm nào đó trong tương lai, (tùy thuộc vào những gì khác đã được gửi đến hàng đợi của bạn), Task 1sẽ thực thi và sau đó Task 2sẽ thực thi.


2
Tôi nghĩ rằng bạn nhận được trật tự sai. ví dụ đầu tiên là asyncphiên bản không chặn
Bryan Chen

Tôi đã chỉnh sửa câu trả lời của bạn theo ý tôi . Nếu đây không phải là trường hợp, xin vui lòng thay đổi nó và làm rõ.
JRG-Nhà phát triển

1
Điều gì sẽ xảy ra nếu bạn gọi Clark_sync và sau đó Clark_async trên cùng một hàng đợi? (và ngược lại)
0xSina

1
Trên hàng đợi nối tiếp, cả hai tác vụ vẫn được thực hiện lần lượt. Trong trường hợp đầu tiên, người gọi đợi khối thứ nhất kết thúc nhưng không đợi khối thứ hai. Trong trường hợp thứ hai, người gọi không đợi khối thứ nhất kết thúc mà đợi khối thứ hai. Nhưng vì hàng đợi thực hiện các khối theo thứ tự, người gọi thực sự chờ đợi cả hai kết thúc.
gnasher729

1
Một khối cũng có thể thực hiện một Clark_async trên hàng đợi của chính nó (thêm các khối tiếp theo sẽ được thực hiện sau); Clark_sync trên hàng đợi nối tiếp hoặc hàng đợi chính sẽ bế tắc. Trong tình huống này, người gọi sẽ đợi khối ban đầu kết thúc, nhưng không phải cho các khối khác. Chỉ cần nhớ: Clark_sync đặt khối ở cuối hàng đợi, hàng đợi thực thi mã cho đến khi khối đó kết thúc, và sau đó Clark_sync trả về. Clark_async chỉ cần thêm khối vào cuối hàng đợi.
gnasher729

5

Tất cả đều liên quan đến hàng đợi chính. Có 4 hoán vị.

i) Hàng đợi nối tiếp, gửi async: Ở đây, các tác vụ sẽ thực hiện lần lượt từng cái, nhưng luồng chính (hiệu ứng trên UI) sẽ không chờ trả về

ii) Hàng đợi nối tiếp, đồng bộ hóa công văn: Ở đây các tác vụ sẽ thực hiện lần lượt từng cái, nhưng luồng chính (hiệu ứng trên UI) sẽ hiển thị độ trễ

iii) Hàng đợi đồng thời, gửi async: Tại đây, các tác vụ sẽ thực thi song song và luồng chính (hiệu ứng trên UI) sẽ không chờ trả về và sẽ trơn tru.

iv) Hàng đợi đồng thời, đồng bộ hóa công văn: Ở đây các tác vụ sẽ thực thi song song, nhưng luồng chính (hiệu ứng trên UI) sẽ hiển thị độ trễ

Sự lựa chọn của bạn về hàng đợi đồng thời hoặc nối tiếp phụ thuộc vào việc bạn có cần một đầu ra từ một nhiệm vụ trước cho nhiệm vụ tiếp theo hay không. Nếu bạn phụ thuộc vào tác vụ trước, hãy chấp nhận hàng đợi nối tiếp khác nhận hàng đợi đồng thời.

Và cuối cùng, đây là một cách thâm nhập trở lại chủ đề chính khi chúng tôi hoàn thành công việc của mình:

DispatchQueue.main.async {
     // Do something here
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.