iOS Concurrency Programming Guide
iOS 和 Mac OS 傳統的并發編程模型是線程,不過線程模型伸縮性不強,而且編寫正確的線程代碼也不容易。Mac OS 和 iOS 采取 asynchronous design approach 來解決并發的問題。
引入的異步技術有兩個:
Grand Central Dispatch:系統管理線程,你不需要編寫線程代碼。只需定義想要執行的任務,然后添加到適當的dispatch queue。Grand Central Dispatch會負責創建線程和調度你的任務。系統直接提供線程管理,比應用實現更加高效。
Operation Queue:Objective-C對象,類似于dispatch queue。你定義想要執行的任務,并添加任務到operation queue,后者負責調度和執行這些任務。和Grand Central Dispatch一樣,Operation Queue也管理了線程,更加高效。
Dispatch Queue
基于C的執行自定義任務機制。dispatch queue按先進先出的順序,串行或并發地執行任務。serial dispaptch queue一次只能執行一個任務,直接當前任務完成才開始出列并啟動下一個任務。而concurrent dispatch queue則盡可能多地啟動任務并發執行。
優點:
直觀而簡單的編程接口
提供自動和整體的線程池管理
提供匯編級調優的速度
更加高效地使用內存
不會trap內核under load
異步分派任務到dispatch queue不會導致queue死鎖
伸縮性強
serial dispatch queue比鎖和其它同步原語更加高效
Dispatch Sources
Dispatch Sources 是基于C的系統事件異步處理機制。一個Dispatch Source封裝了一個特定類型的系統事件,當事件發生時提交一個特定的block對象或函數到dispatch queue。你可以使用Dispatch Sources監控以下類型的系統事件:
定時器
信號處理器
描述符相關的事件
進程相關的事件
Mach port事件
你觸發的自定義事件
Operation Queues
Operation Queues是Cocoa版本的并發dispatch queue,由 NSOperationQueue 類實現。dispatch queue總是按先進先出的順序執行任務,而 Operation Queues 在確定任務執行順序時,還會考慮其它因素。最主要的一個因素是指定任務是否依賴于另一個任務的完成。你在定義任務時配置依賴性,從而創建復雜的任務執行順序圖
提交到Operation Queues的任務必須是 NSOperation 對象,operation object封裝了你要執行的工作,以及所需的所有數據。由于 NSOperation 是一個抽象基類,通常你需要定義自定義子類來執行任務。不過Foundation framework自帶了一些具體子類,你可以創建并執行相關的任務。
Operation objects會產生key-value observing(KVO)通知,對于監控任務的進程非常有用。雖然operation queue總是并發地執行任務,你可以使用依賴,在需要時確保順序執行
異步設計技術
通過確保主線程自由響應用戶事件,并發可以很好地提高應用的響應性。通過將工作分配到多核,還能提高應用處理的性能。但是并發也帶來一定的額外開銷,并且使代碼更加復雜,更難編寫和調試代碼。
因此在應用設計階段,就應該考慮并發,設計應用需要執行的任務,及任務所需的數據結構。
Operation Queues
基于Objective-C,因此基于Cocoa的應用通常會使用Operation Queues
Operation Objects
operation object 是 NSOperation 類的實例,封裝了應用需要執行的任務,和執行任務所需的數據。NSOperation 本身是抽象基類,我們必須實現子類。Foundation framework提供了兩個具體子類,你可以直接使用:
類 描述
NSInvocationOperation 可以直接使用的類,基于應用的一個對象和selector來創建operation object。如果你已經有現有的方法來執行需要的任務,就可以使用這個類。
NSBlockOperation 可以直接使用的類,用來并發地執行一個或多個block對象。operation object使用“組”的語義來執行多個block對象,所有相關的block都執行完成之后,operation object才算完成。
NSOperation 基類,用來自定義子類operation object。繼承NSOperation可以完全控制operation object的實現,包括修改操作執行和狀態報告的方式。
所有operation objects都支持以下關鍵特性:
支持建立基于圖的operation objects依賴。可以阻止某個operation運行,直到它依賴的所有operation都已經完成。
支持可選的completion block,在operation的主任務完成后調用。
支持應用使用KVO通知來監控operation的執行狀態。
支持operation優先級,從而影響相對的執行順序
支持取消,允許你中止正在執行的任務
并發 VS 非并發Operations
通常我們通過將operation添加到operation queue中來執行該操作。但是我們也可以手動調用start方法來執行一個operation對象,這樣做不保證operation會并發執行。NSOperation類對象的 isConcurrent 方法告訴你這個operation相對于調用start方法的線程,是同步還是異步執行的。isConcurrent 方法默認返回NO,表示operation與調用線程同步執行。
如果你需要實現并發operation,也就是相對調用線程異步執行的操作。你必須添加額外的代碼,來異步地啟動操作。例如生成一個線程、調用異步系統函數,以確保start方法啟動任務,并立即返回。
多數開發者從來都不需要實現并發operation對象,我們只需要將operations添加到operation queue。當你提交非并發operation到operation queue時,queue會創建線程來運行你的操作,因此也能達到異步執行的目的。只有你不希望使用operation queue來執行operation時,才需要定義并發operations。
創建一個 NSInvocationOperation 對象
如果已經現有一個方法,需要并發地執行,就可以直接創建 NSInvocationOperation 對象,而不需要自己繼承 NSOperation。
@implementation MyCustomClass
- (NSOperation)taskWithData:(id)data {
NSInvocationOperation theOp = [[[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(myTaskMethod:) object:data] autorelease];
return theOp;
}
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
// Perform the task.
}
@end
創建一個 NSBlockOperation 對象
NSBlockOperation 對象用于封裝一個或多個block對象,一般創建時會添加至少一個block,然后再根據需要添加更多的block。當 NSBlockOperation 對象執行時,會把所有block提交到默認優先級的并發dispatch queue。然后 NSBlockOperation 對象等待所有block完成執行,最后標記自己已完成。因此可以使用block operation來跟蹤一組執行中的block,有點類似于thread join等待多個線程的結果。區別在于block operation本身也運行在一個單獨的線程,應用的其它線程在等待block operation完成時可以繼續工作。
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Beginning operation.\n");
// Do some work.
}];
使用 addExecutionBlock: 可以添加更多block到這個block operation對象。如果需要順序地執行block,你必須直接提交到所需的dispatch queue。
自定義Operation對象
如果block operation和invocation operation對象不符合應用的需求,你可以直接繼承 NSOperation,并添加任何你想要的行為。NSOperation 類提供通用的子類繼承點,而且實現了許多重要的基礎設施來處理依賴和KVO通知。繼承所需的工作量主要取決于你要實現非并發還是并發的operation。
定義非并發operation要簡單許多,只需要執行主任務,并正確地響應取消事件;NSOperation 處理了其它所有事情。對于并發operation,你必須替換某些現有的基礎設施代碼。
執行主任務
每個operation對象至少需要實現以下方法:
自定義initialization方法:初始化,將operation 對象設置為已知狀態
自定義main方法:執行你的任務
你也可以選擇性地實現以下方法:
main方法中需要調用的其它自定義方法
Accessor方法:設置和訪問operation對象的數據
dealloc方法:清理operation對象分配的所有內存
NSCoding 協議的方法:允許operation對象archive和unarchive
@interface MyNonConcurrentOperation : NSOperation {
id myData;
}
-(id)initWithData:(id)data;
@end
@implementation MyNonConcurrentOperation
(id)initWithData:(id)data {
if (self = [super init])
myData = [data retain];
return self;
}(void)dealloc {
[myData release];
[super dealloc];
}
-(void)main {
@try {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Do some work on myData and report the results.
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
@end
響應取消事件
operation開始執行之后,會一直執行任務直到完成,或者顯式地取消操作。取消可能在任何時候發生,甚至在operation執行之前。盡管 NSOperation 提供了一個方法,讓應用取消一個操作,但是識別出取消事件則是你的事情。如果operation直接終止,可能無法回收所有已分配的內存或資源。因此operation對象需要檢測取消事件,并優雅地退出執行。
operation 對象定期地調用 isCancelled 方法,如果返回YES(表示已取消),則立即退出執行。不管是自定義 NSOperation 子類,還是使用系統提供的兩個具體子類,都需要支持取消。isCancelled方法本身非常輕量,可以頻繁地調用而不產生大的性能損失。以下地方可能需要調用isCancelled:
在執行任何實際的工作之前
在循環的每次迭代過程中,如果每個迭代相對較長可能需要調用多次
代碼中相對比較容易中止操作的任何地方
- (void)main {
@try {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
注意你的代碼還需要完成所有相關的資源清理工作
為并發執行配置operations
Operation對象默認按同步方式執行,也就是在調用start方法的那個線程中直接執行。由于operation queue為非并發operation提供了線程支持,對應用來說,多數operations仍然是異步執行的。但是如果你希望手工執行operations,而且仍然希望能夠異步執行操作,你就必須采取適當的措施,通過定義operation對象為并發操作來實現。
方法 描述
start (必須)所有并發操作都必須覆蓋這個方法,以自定義的實現替換默認行為。手動執行一個操作時,你會調用start方法。因此你對這個方法的實現是操作的起點,設置一個線程或其它執行環境,來執行你的任務。你的實現在任何時候都絕對不能調用super。
main (可選)這個方法通常用來實現operation對象相關聯的任務。盡管你可以在start方法中執行任務,使用main來實現任務可以讓你的代碼更加清晰地分離設置和任務代碼
isExecuting
isFinished (必須)并發操作負責設置自己的執行環境,并向外部client報告執行環境的狀態。因此并發操作必須維護某些狀態信息,以知道是否正在執行任務,是否已經完成任務。使用這兩個方法報告自己的狀態。
這兩個方法的實現必須能夠在其它多個線程中同時調用。另外這些方法報告的狀態變化時,還需要為相應的key path產生適當的KVO通知。
isConcurrent (必須)標識一個操作是否并發operation,覆蓋這個方法并返回YES
@interface MyOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
(id)init {
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}(BOOL)isConcurrent {
return YES;
}(BOOL)isExecuting {
return executing;
}(BOOL)isFinished {
return finished;
}(void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
@try {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Do the main work of the operation here.
[self completeOperation];
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
@end
即使操作被取消,你也應該通知KVO observers,你的操作已經完成。當某個operation對象依賴于另一個operation對象的完成時,它會監測后者的isFinished key path。只有所有依賴的對象都報告已經完成,第一個operation對象才會開始運行。如果你的operation對象沒有產生完成通知,就會阻止其它依賴于你的operation對象運行。
維護KVO依從
NSOperation類的key-value observing(KVO)依從于以下key paths:
isCancelled
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock
如果你覆蓋start方法,或者對NSOperation對象的其它自定義運行(覆蓋main除外),你必須確保自定義對象對這些key paths保留KVO依從。覆蓋start方法時,需要關注isExecuting和isFinished兩個key paths。
如果你希望實現依賴于其它東西(非operation對象),你可以覆蓋isReady方法,并強制返回NO,直到你等待的依賴得到滿足。如果你需要保留默認的依賴管理系統,確保你調用了[super isReady]。當你的operation對象的準備就緒狀態發生改變時,生成一個isReady的key path的KVO通知。
除非你覆蓋了 addDependency: 或 removeDependency: 方法,否則你不需要關注dependencies key path
雖然你也可以生成 NSOperation 的其它KVO通知,但通常你不需要這樣做。如果需要取消一個操作,你可以直接調用現有的cancel方法。類似地,你也很少需要修改queue優先級信息。最后,除非你的operation對象可以動態地改變并發狀態,你也不需要提供isConcurrent key path的KVO通知。
自定義一個Operation對象的執行行為
對Operation對象的配置發生在創建對象之后,將其添加到queue之前。
配置operation之間的依賴關系
依賴關系可以順序地執行相關的operation對象,依賴于其它操作,則必須等到該操作完成之后自己才能開始。你可以創建一對一的依賴關系,也可以創建多個對象之間的依賴圖。
使用 NSOperation 的 addDependency: 方法在兩個operation對象之間建立依賴關系。表示當前operation對象將依賴于參數指定的目標operation對象。依賴關系不局限于相同queue中的operations對象,Operation對象會管理自己的依賴,因此完全可以在不同的queue之間的Operation對象創建依賴關系。
唯一的限制是不能創建環形依賴,這是程序員的錯誤,所有受影響的operations都無法運行!
當一個operation對象依賴的所有其它對象都已經執行完成,該operation就變成準備執行狀態(如果你自定義了isReady方法,則由你的方法確定是否準備好運行)。如果operation已經在一個queue中,queue就可以在任何時候執行這個operation。如果你需要手動執行該operation,就自己調用operation的start方法。
配置依賴必須在運行operation和添加operation到queue之前進行,之后添加的依賴關系可能不起作用。
依賴要求每個operation對象在狀態發生變化時必須發出適當的KVO通知。如果你自定義了operation對象的行為,就必須在自定義代碼中生成適當的KVO通知,以確保依賴能夠正確地執行。
修改Operation的執行優先級
對于添加到queue的Operations,執行順序首先由已入隊列的operations是否準備好,然后再根據所有operations的相對優先級確定。是否準備好由對象的依賴關系確定,優先級等級則是operation對象本身的一個屬性。默認所有operation都擁有“普通”優先級,不過你可以通過 setQueuePriority: 方法來提升或降低operation對象的優先級。
優先級只能應用于相同queue中的operations。如果應用有多個operation queue,每個queue的優先級等級是互相獨立的。因此不同queue中的低優先級操作仍然可能比高優先級操作更早執行。
優先級不能替代依賴關系,優先級只是queue對已經準備好的operations確定執行順序。先滿足依賴關系,然后再根據優先級從所有準備好的操作中選擇優先級最高的那個執行。
修改底層線程的優先級
Mac OS X 10.6之后,我們可以配置operation底層線程的執行優先級,線程直接由內核管理,通常優先級高的線程會給予更多的執行機會。對于operation對象,你指定線程優先級為0.0到1.0之間的某個數值,0.0表示最低優先級,1.0表示最高優先級。默認線程優先級為0.5
要設置operation的線程優先級,你必須在將operation添加到queue之前,調用 setThreadPriority: 方法進行設置。當queue執行該operation時,默認的start方法會使用你指定的值來修改當前線程的優先級。不過新的線程優先級只在operation的main方法范圍內有效。其它所有代碼仍然(包括completion block)運行在默認線程優先級。
如果你創建了并發operation,并覆蓋了start方法,你必須自己配置線程優先級。
設置一個completion block
在Mac OS X 10.6之后,operation可以在主任務完成之后執行一個completion block。你可以使用這個completion block來執行任何不屬于主任務的工作。例如你可以使用這個block來通知相關的client,操作已經執行完成。而并發operation對象則可以使用這個block來產生最終的KVO通知。
調用 NSOperation 的 setCompletionBlock: 方法來設置一個completion block,你傳遞的block應該沒有參數和返回值。
實現Operation對象的技巧
Operation對象的內存管理
operation對象需要良好的內存管理策略
創建你自己的Autorelease Pool
operation是Objective-C對象,你在實現任務的代碼中應該創建一個autorelease pool,這樣可以保護那些autorelease對象得到盡快地釋放。雖然你的自定義代碼執行時可能已經有了一個pool,但你不能依賴于這個行為,總是應該自己創建一個。
擁有自己的autorelease pool還能更加靈活地管理operation的內存。如果operation創建大量的臨時對象,則可以考慮創建額外的pool,來清理不再使用的臨時對象。在iOS*****別需要注意,應遲早地清理不再使用的臨時對象,避免內存警告。
- (void)main {
@try {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Do the main work of the operation here.
[pool release];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
避免Per-Thread存儲
雖然多數operation都在線程中執行,但對于非并發operation,通常由operation queue提供線程,這時候queue擁有該線程,而你的應用不應該去動這個線程。特別是不要關聯任何數據到不是你創建和擁有的線程。這些線程由queue管理,根據系統和應用的需求創建或銷毀。因此使用Per-Thread storage在operations之間傳遞數據是不可靠的,而且很有可能會失敗。
對于operation對象,你完全沒有理由使用Per-Thread Storage,應該在創建對象的時候就給它需要的所有數據。所有輸入和輸出數據都應該存儲在operation對象中,最后再整合到你的應用,或者最終釋放掉。
根據需要保留Operation對象的引用
由于operation對象異步執行,你不能創建完以后就完全不管。它們也是對象,需要你來分配和釋放它們管理的任何資源,特別是如果你需要在operation對象完成后獲取其中的數據。
由于queue總是盡最大可能快速地調度和執行operation,在你添加operation到queue時,可能立即就開始運行,當你稍后向queue請求operation對象的狀態時,有可能queue已經執行完了相應的operation并從queue中刪除了這個對象。因此你總是應該自己擁有operation對象的引用。
處理錯誤和異常
operation本質上是應用中獨立的實體,因此需要自己負責處理所有的錯誤和異常。NSOperation默認的start方法并沒有捕獲異常。所以你自己的代碼總是應該捕獲并抑制異常。你還應該檢查錯誤代碼并適當地通知應用。如果你覆蓋了start方法,你也必須捕獲所有異常,阻止它離開底層線程的范圍。
你需要準備好處理以下錯誤或異常:
檢查并處理UNIX errno風格的錯誤代碼
檢查方法或函數顯式返回的錯誤代碼
捕獲你的代碼或系統frameworks拋出的異常
捕獲NSOperation類自己拋出的異常,在以下情況NSOperation會拋出異常:
operation沒有準備好,但是調用了start方法
operation正在執行或已經完成(可能被取消),再次調用了start方法。
當你添加completion block到正在執行或已經完成的operation
當你試圖獲取已經取消 NSInvocationOperation 對象的結果
為Operation對象確定一個適當的范圍
和任何對象一樣,NSOperation對象也會消耗內存,執行時也會帶來開銷。因此如果operation對象只做很少的工作,但是卻創建成千上萬個小的operation對象,你就會發現更多的時間花在了調度operations而不是執行它們。
要高效地使用Operations,關鍵是在Operation執行的工作量和保持計算機繁忙之間,找到最佳的平衡。確保每個Operation都有一定的工作量可以執行。例如100個operations執行100次相同任務,可以考慮換成10個operations,每個執行10次。
你同樣要避免向一個queue中添加過多的operations,或者持續快速地向queue中添加operation,超過queue所能處理的能力。這里可以考慮分批創建operations對象,在一批對象執行完之后,使用completion block告訴應用創建下一批operations對象。
執行Operations
應用需要執行Operations來處理相關的工作,你有幾種方法來執行Operations對象。
添加Operations到Operation Queue
執行Operations最簡單的方法是添加到operation queue,后者是 NSOperationQueue 對象。應用負責創建和維護自己使用的所有 NSOperationQueue 對象。
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
調用 addOperation: 方法添加一個operation到queue,Mac OS X 10.6之后可以使用 addOperations:waitUntilFinished: 方法一次添加一組operations,或者也可以直接使用 addOperationWithBlock: 方法添加 block 對象到queue。
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
/* Do something. */
}];
Operations添加到queue后,通常短時間內就會得到運行。但是如果存在依賴,或者Operations掛起等原因,也可能需要等待。
注意Operations添加到queue之后,絕對不要再修改Operations對象。因為Operations對象可能會在任何時候運行,因此改變依賴或數據會產生不利的影響。你只能通過 NSOperation 的方法來查看操作的狀態,是否正在運行、等待運行、已經完成等。
雖然 NSOperationQueue 類設計用于并發執行Operations,你也可以強制單個queue一次只能執行一個Operation。setMaxConcurrentOperationCount: 方法可以配置operation queue的最大并發操作數量。設為1就表示queue每次只能執行一個操作。不過operation執行的順序仍然依賴于其它因素,像操作是否準備好和優先級等。因此串行化的operation queue并不等同于Grand Central Dispatch中的串行dispatch queue。
手動執行Operations
手動執行Operation,要求Operation已經準備好,isReady返回YES,此時你才能調用start方法來執行它。isReady方法與Operations依賴是結合在一起的。
調用start而不是main來手動執行Operation,因為start在執行你的自定義代碼之前,會首先執行一些安全檢查。而且start還會產生KVO通知,以正確地支持Operations的依賴機制。start還能處理Operations已經被取消的情況,此時會拋出一個異常。
手動執行Operation對象之前,還需要調用 isConcurrent 方法,如果返回NO,你的代碼可以決定在當前線程同步執行這個Operation,或者創建一個獨立的線程以異步執行。
下面方法演示了手動執行Operation,如果這個方法返回NO,表示不能執行,你需要設置一個定時器,稍后再次調用本方法,直到這個方法返回YES,表示已經執行Operation。
- (BOOL)performOperation:(NSOperation*)anOp
{
BOOL ranIt = NO;
if ([anOp isReady] && ![anOp isCancelled])
{
if (![anOp isConcurrent])
[anOp start];
else
[NSThread detachNewThreadSelector:@selector(start)
toTarget:anOp withObject:nil];
ranIt = YES;
}
else if ([anOp isCancelled])
{
// If it was canceled before it was started,
// move the operation to the finished state.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
// Set ranIt to YES to prevent the operation from
// being passed to this method again in the future.
ranIt = YES;
}
return ranIt;
}
取消Operations
一旦添加到operation queue,queue就擁有了這個對象并且不能被刪除,唯一能做的事情是取消。你可以調用Operation對象的cancel方法取消單個操作,也可以調用operation queue的 cancelAllOperations 方法取消當前queue中的所有操作。
只有你確定不再需要Operations對象時,才應該取消它。發出取消命令會將Operations對象設置為"Canceled"狀態,會阻止它被執行。由于取消也被認為是完成,依賴于它的其它Operations對象會收到適當的KVO通知,并清除依賴狀態,然后得到執行。
因此常見的做法是當發生重大事件時,一次性取消queue中的所有操作,例如應用退出或用戶請求取消操作。
等待Operations完成
為了最佳的性能,你應該盡量設計你的應用盡可能地異步操作,讓應用在操作正在執行時可以去處理其它事情。
如果創建operation的代碼需要處理operation完成后的結果,可以使用 NSOperation 的 waitUntilFinished 方法等待operation完成。通常我們應該避免編寫這樣的代碼,阻塞當前線程可能是一種簡便的解決方案,但是它引入了更多的串行代碼,限制了整個應用的并發性,同時也降低了用戶體驗。
絕對不要在應用主線程中等待一個Operation,只能在第二或次要線程中等待。阻止主線程將導致應用無法響應用戶事件,應用也將表現為無響應。
除了等待單個Operation完成,你也可以同時等待一個queue中的所有操作,使用 NSOperationQueue 的 waitUntilAllOperationsAreFinished 方法。注意在等待一個queue時,應用的其它線程仍然可以往queue中添加Operation,因此可能加長你線程的等待時間。
掛起和繼續Queue
如果你想臨時掛起Operations的執行,可以使用 setSuspended: 方法暫停相應的queue。不過掛起一個queue不會導致正在執行的Operation在任務中途暫停,只是簡單地阻止調度新Operation執行。你可以在響應用戶請求時,掛起一個queue,來暫停等待中的任務。稍后根據用戶的請求,可以再次調用 setSuspended: 方法繼續Queue中操作的執行。