iOS 併發編程(1)

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 app

基於C的執行自定義任務機制。dispatch queue按先進先出的順序,串行或併發地執行任務。serial dispaptch queue一次只能執行一個任務,直接當前任務完成纔開始出列並啓動下一個任務。而concurrent dispatch queue則儘量多地啓動任務併發執行。 iphone

優勢: 異步

直觀而簡單的編程接口 async

提供自動和總體的線程池管理 ide

提供彙編級調優的速度

更加高效地使用內存

不會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。

 
  1. @implementation MyCustomClass 
  2. - (NSOperation*)taskWithData:(id)data { 
  3. NSInvocationOperation* theOp = [[[NSInvocationOperation alloc] initWithTarget:self 
  4. selector:@selector(myTaskMethod:) object:data] autorelease]; 
  5.  
  6. return theOp; 
  7.  
  8. // This is the method that does the actual work of the task. 
  9. - (void)myTaskMethod:(id)data { 
  10. // Perform the task. 
  11. @end 

建立一個 NSBlockOperation 對象

NSBlockOperation 對象用於封裝一個或多個block對象,通常建立時會添加至少一個block,而後再根據須要添加更多的block。當 NSBlockOperation 對象執行時,會把全部block提交到默認優先級的併發dispatch queue。而後 NSBlockOperation 對象等待全部block完成執行,最後標記本身已完成。所以可使用block operation來跟蹤一組執行中的block,有點相似於thread join等待多個線程的結果。區別在於block operation自己也運行在一個單獨的線程,應用的其它線程在等待block operation完成時能夠繼續工做。

 
  1. NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{ 
  2. NSLog(@"Beginning operation.\n"); 
  3. // Do some work. 
  4. }]; 

使用 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

 
  1. @interface MyNonConcurrentOperation : NSOperation { 
  2. id myData; 
  3. -(id)initWithData:(id)data; 
  4. @end 
  5.  
  6. @implementation MyNonConcurrentOperation 
  7. - (id)initWithData:(id)data { 
  8. if (self = [super init]) 
  9. myData = [data retain]; 
  10. return self; 
  11.  
  12. - (void)dealloc { 
  13. [myData release]; 
  14. [super dealloc]; 
  15.  
  16. -(void)main { 
  17. @try { 
  18. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 
  19. // Do some work on myData and report the results. 
  20. [pool release]; 
  21. @catch(...) { 
  22. // Do not rethrow exceptions. 
  23. @end 

響應取消事件

operation開始執行以後,會一直執行任務直到完成,或者顯式地取消操做。取消可能在任什麼時候候發生,甚至在operation執行以前。儘管 NSOperation 提供了一個方法,讓應用取消一個操做,可是識別出取消事件則是你的事情。若是operation直接終止,可能沒法回收全部已分配的內存或資源。所以operation對象須要檢測取消事件,並優雅地退出執行。

operation 對象按期地調用 isCancelled 方法,若是返回YES(表示已取消),則當即退出執行。無論是自定義 NSOperation 子類,仍是使用系統提供的兩個具體子類,都須要支持取消。isCancelled方法自己很是輕量,能夠頻繁地調用而不產生大的性能損失。如下地方可能須要調用isCancelled:

在執行任何實際的工做以前

在循環的每次迭代過程當中,若是每一個迭代相對較長可能須要調用屢次

代碼中相對比較容易停止操做的任何地方

 
  1. - (void)main { 
  2. @try { 
  3. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
  4. BOOL isDone = NO; 
  5.  
  6. while (![self isCancelled] && !isDone) { 
  7. // Do some work and set isDone to YES when finished 
  8. [pool release]; 
  9. @catch(...) { 
  10. // 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
 
  1. @interface MyOperation : NSOperation { 
  2. BOOL        executing; 
  3. BOOL        finished; 
  4. - (void)completeOperation; 
  5. @end 
  6.  
  7. @implementation MyOperation 
  8. - (id)init { 
  9. self = [super init]; 
  10. if (self) { 
  11. executing = NO; 
  12. finished = NO; 
  13. return self; 
  14.  
  15. - (BOOL)isConcurrent { 
  16. return YES; 
  17.  
  18. - (BOOL)isExecuting { 
  19. return executing; 
  20.  
  21. - (BOOL)isFinished { 
  22. return finished; 
  23.  
  24. - (void)start { 
  25. // Always check for cancellation before launching the task. 
  26. if ([self isCancelled]) 
  27. // Must move the operation to the finished state if it is canceled. 
  28. [self willChangeValueForKey:@"isFinished"]; 
  29. finished = YES; 
  30. [self didChangeValueForKey:@"isFinished"]; 
  31. return
  32.  
  33. // If the operation is not canceled, begin executing the task. 
  34. [self willChangeValueForKey:@"isExecuting"]; 
  35. [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; 
  36. executing = YES; 
  37. [self didChangeValueForKey:@"isExecuting"]; 
  38.  
  39. - (void)main { 
  40. @try { 
  41. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 
  42.  
  43. // Do the main work of the operation here. 
  44.  
  45. [self completeOperation]; 
  46. [pool release]; 
  47. @catch(...) { 
  48. // Do not rethrow exceptions. 
  49.  
  50. - (void)completeOperation { 
  51. [self willChangeValueForKey:@"isFinished"]; 
  52. [self willChangeValueForKey:@"isExecuting"]; 
  53.  
  54. executing = NO; 
  55. finished = YES; 
  56.  
  57. [self didChangeValueForKey:@"isExecuting"]; 
  58. [self didChangeValueForKey:@"isFinished"]; 
  59. @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*****別須要注意,應早晚地清理再也不使用的臨時對象,避免內存警告。

 
  1. - (void)main { 
  2. @try { 
  3. NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 
  4.  
  5. // Do the main work of the operation here. 
  6.  
  7. [pool release]; 
  8. @catch(...) { 
  9. // 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 對象。

 
  1. NSOperationQueue* aQueue = [[NSOperationQueue alloc] init]; 

調用 addOperation: 方法添加一個operation到queue,Mac OS X 10.6以後可使用 addOperations:waitUntilFinished: 方法一次添加一組operations,或者也能夠直接使用 addOperationWithBlock: 方法添加 block 對象到queue。

 
  1. [aQueue addOperation:anOp]; // Add a single operation 
  2. [aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations 
  3. [aQueue addOperationWithBlock:^{ 
  4. /* Do something. */ 
  5. }]; 

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。

 
  1. - (BOOL)performOperation:(NSOperation*)anOp 
  2. BOOL        ranIt = NO; 
  3.  
  4. if ([anOp isReady] && ![anOp isCancelled]) 
  5. if (![anOp isConcurrent]) 
  6. [anOp start]; 
  7. else 
  8. [NSThread detachNewThreadSelector:@selector(start) 
  9. toTarget:anOp withObject:nil]; 
  10. ranIt = YES; 
  11. else if ([anOp isCancelled]) 
  12. // If it was canceled before it was started, 
  13. //  move the operation to the finished state. 
  14. [self willChangeValueForKey:@"isFinished"]; 
  15. [self willChangeValueForKey:@"isExecuting"]; 
  16. executing = NO; 
  17. finished = YES; 
  18. [self didChangeValueForKey:@"isExecuting"]; 
  19. [self didChangeValueForKey:@"isFinished"]; 
  20.  
  21. // Set ranIt to YES to prevent the operation from 
  22. // being passed to this method again in the future. 
  23. ranIt = YES; 
  24. 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中操做的執行。

1 2  3  4 
相關文章
相關標籤/搜索