GCD để thực hiện nhiệm vụ trong luồng chính


254

Tôi có một cuộc gọi lại có thể đến từ bất kỳ chủ đề. Khi tôi nhận được cuộc gọi lại này, sau đó tôi muốn thực hiện một tác vụ nhất định trên luồng chính.

Tôi có cần kiểm tra xem tôi đã ở trên luồng chính chưa - hoặc có bất kỳ hình phạt nào khi không thực hiện kiểm tra này khi gọi mã dưới đây?

dispatch_async(dispatch_get_main_queue(), ^{
   // do work here
});

116
Năm năm sau tôi vẫn không thể nhớ cú pháp của các khối GCD và kết thúc ở đây mọi lúc.
SpaceTrucker

7
@SpaceTrucker - Đó là lý do tương tự Tôi ở trang này: D
Matthew Cawley

4
9 năm sau, và tôi vẫn đến để sao chép cú pháp từ trang này.
Osa

Câu trả lời:


152

Không, bạn không cần kiểm tra xem bạn đã vào chủ đề chính chưa. Bằng cách gửi khối tới hàng đợi chính, bạn chỉ cần lập lịch cho khối được thực hiện một cách an toàn trên luồng chính, điều này xảy ra khi vòng chạy tương ứng được chạy.

Nếu bạn đã ở trên luồng chính, thì hành vi là như nhau: khối được lên lịch và được thực thi khi vòng lặp chạy của luồng chính được chạy.


3
Câu hỏi là liệu có "hình phạt bằng cách không thực hiện kiểm tra này" ... Tôi sẽ nghĩ rằng có một hình phạt hiệu suất cho việc sử dụng công văn không đồng bộ khi không cần thiết, hoặc nó là tầm thường?
Dan Rosenstark

1
@Yar Tôi không nghĩ rằng có một tác động hiệu suất đáng chú ý trong hầu hết các trường hợp: GCD là một thư viện nhẹ. Điều đó nói rằng, tôi hiểu câu hỏi như: 'đưa ra đoạn mã dưới đây, tôi có cần kiểm tra xem tôi có ở trên luồng chính không?'

7
Tuy nhiên, bạn cần phải kiểm tra xem bạn có sử dụng Clark_sync không. Nếu không bạn sẽ gặp bế tắc.
Victor Engel

Nếu bạn đang ở trên hàng đợi chính và gửi asynctrở lại vào hàng đợi chính, nó sẽ được chạy nhưng điều đó có thể làm rối loạn thời gian dự kiến ​​của các hành động của bạn . Chẳng hạn như mã UI viewDidLoad() không chạy cho đến khi chế độ xem được hiển thị lần đầu tiên .
pkamb

106

Đối với trường hợp điều phối không đồng bộ mà bạn mô tả ở trên, bạn không cần phải kiểm tra xem bạn có ở trên luồng chính hay không. Như Bavarious chỉ ra, điều này chỉ đơn giản sẽ được xếp hàng để được chạy trên luồng chính.

Tuy nhiên, nếu bạn cố gắng thực hiện các thao tác trên bằng cách sử dụng a dispatch_sync()và cuộc gọi lại của bạn nằm trên luồng chính, ứng dụng của bạn sẽ bế tắc tại thời điểm đó. Tôi mô tả điều này trong câu trả lời của tôi ở đây , bởi vì hành vi này làm tôi ngạc nhiên khi chuyển một số mã từ -performSelectorOnMainThread:. Như tôi đã đề cập ở đó, tôi đã tạo ra một hàm trợ giúp:

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

sẽ chạy một khối đồng bộ trên luồng chính nếu phương thức bạn hiện không có trên luồng chính và chỉ thực hiện khối nội tuyến nếu có. Bạn có thể sử dụng cú pháp như sau để sử dụng:

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

1
Tại sao không gọi nó là "runOnMainQueueSync"? Thực tế là nó có thể bế tắc và không phải là điều tôi không muốn có trong toàn bộ mã của mình. Cảm ơn và +1 như mọi khi.
Dan Rosenstark

2
@Yar - Tôi chỉ đặt cho nó cái tên đó để tôi hiểu rõ lý do tại sao tôi không bắn ra các khối một cách đồng bộ trên hàng đợi chính thay vì sử dụng chức năng trợ giúp này. Đó là một lời nhắc nhở cho chính tôi.
Brad Larson

3
Vẫn có thể bế tắc với chức năng này. Đồng bộ hàng đợi chính> đồng bộ hàng đợi khác> hàng đợi chính sẽ bế tắc.
hfossli

2
@hfossli - Đúng, nó sẽ không xử lý mọi trường hợp. Trong cách sử dụng của tôi, tôi luôn luôn gọi từ một công văn không đồng bộ trên hàng đợi nối tiếp nền hoặc mã nội tuyến trên luồng chính. Tôi tự hỏi nếu dispatch_set_specific()sẽ giúp trong trường hợp bạn mô tả: stackoverflow.com/a/12806754/19679 .
Brad Larson

1
[NSThread isMainThread] thường trả về CÓ và không được coi là an toàn để kiểm tra trường hợp này trong lập trình GCD. stackoverflow.com/questions/14716334/
trộm

57

Như các câu trả lời khác đã đề cập, Clark_async từ luồng chính vẫn ổn.

Tuy nhiên, tùy thuộc vào trường hợp sử dụng của bạn, có một tác dụng phụ mà bạn có thể xem là nhược điểm: vì khối được lên lịch trên hàng đợi, nó sẽ không thực thi cho đến khi điều khiển quay trở lại vòng lặp chạy, điều này sẽ có tác dụng trì hoãn thực hiện khối của bạn.

Ví dụ,

NSLog(@"before dispatch async");
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"inside dispatch async block main thread from main thread");
});
NSLog(@"after dispatch async");

Sẽ in ra:

before dispatch async
after dispatch async
inside dispatch async block main thread from main thread

Vì lý do này, nếu bạn đang mong đợi khối thực thi ở giữa NSLog bên ngoài, thì điều_async sẽ không giúp bạn.


Cần phải nói rằng đây là một điều quan trọng cần xem xét
Marc-Alexandre Bérubé

Vì bạn muốn kiểm tra, điều này có nghĩa là mã có thể hoặc không thể chạy trong luồng chính. Đã có trong chủ đề chính sẽ chạy theo thứ tự đó, nếu không điều này không thể được đảm bảo. Vì vậy, bạn nên tránh thiết kế mã để chạy theo thứ tự cụ thể khi tham khảo chương trình đa luồng. Cố gắng sử dụng cách gọi lại async.
ooops

1

Không, bạn không cần phải kiểm tra nếu bạn đang ở trong chủ đề chính. Đây là cách bạn có thể làm điều này trong Swift:

runThisInMainThread { () -> Void in
    runThisInMainThread { () -> Void in
        // No problem
    }
}

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

Nó được bao gồm như là một chức năng tiêu chuẩn trong repo của tôi, hãy kiểm tra xem: https://github.com/goktugyil/EZSwiftExtensions

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.