【iOS印象】 併發程式設計:Operation Queues

Dylan_王興彬發表於2018-04-07

基本概念
  • 程式(Process):一個正在執行中的可執行檔案。每一個程式都有獨立的記憶體空間和系統資源(埠許可權等),至少包含一個主執行緒和任意數量的其他(輔)執行緒。當一個程式的主執行緒退出時,則該程式即結束了。
  • 執行緒(Thread):一個獨立的程式碼執行路徑,也即執行緒是程式碼執行路徑的最小分支。iOS 中,執行緒底層基於 POSIX threads APIs,即 pthreads。
  • 任務(Task):需要執行的工作(一段程式碼),是個抽象概念。
  • 序列 vs. 併發:主要區別在於允許同時執行的任務數量
    • 序列:一次只能執行一個任務,必須等一個任務執行完成後才執行下一個任務。
    • 併發:指允許多個任務同時執行。(note: 其實也是某一時刻只執行一個任務,系統在多個任務之間進行快速切換,區別於並行)
  • 同步 vs. 非同步:主要區別在於是否等待任務執行完成,即是否阻塞當前執行緒。
    • 同步:會等待執行完成後再繼續執行接下來的程式碼
    • 非同步:呼叫後立即返回,不會等待執行操作的執行結果
  • 佇列 vs. 執行緒
    • 佇列:iOS 中,分為序列佇列與併發佇列,用於處理不同需要的任務。
    • 執行緒:iOS 系統使用佇列進行任務排程,根據任務需要和系統負載情況動態地建立和銷燬執行緒,而無須手動管理。

iOS 的併發程式設計模型

在 iOS 中,蘋果與傳統的基於執行緒不同,而是採用佇列,一般只需要定義好排程的任務,並加入到相應的佇列,系統就會在合適的執行緒執行這些任務,而不需要關心執行緒的建立和銷燬。

以下情景應該直接使用執行緒:
  • 用執行緒以外的方式無法實現的特定任務
  • 必須實時執行一個任務
  • 對在後臺執行的任務有更多的可預測行為

Operation Queues vs. Grand Central Dispatch (GCD)

  • Operation Queues: 相對增加了一點開銷,更靈活,可通過給 operation 之間新增依賴關係,開始、暫停、恢復,或取消 operation
  • GCD:輕量級,以 FIFO 的順序執行併發任務。使用 GCD 時,我們不關心任務具體的排程情況,而是由系統處理,因此也相對不夠靈活

關於 Operation 物件

用於封裝需要執行的任務,Operation 本身是一個抽象類,使用時必須建立自定義子類或使用系統預定義的子類,NSInvocationOperation 和 BlockOperation

  • NSInvocationOperation: 通過一個 object 和 selector 建立一個非併發的(non-concurrent) operation(Swift 不可用,BlockOperation 或 OperationQueue.addOperation(block:) 替代)
  • BlockOperation: 可用來併發執行一個或多個 block,只有當一個 BlockOperation 關聯的所有 block 執行完畢,這個 operation 才算執行完畢,類似 dispatch_group

另外,所有 operation 都支援以下特性:
  • 支援在 operation 之間建立依賴關係,只有當一個 operation 所依賴的所有 operation 都完成時,這個 operation 才能開始執行;
  • 支援一個可選的 completion block,會在主任務執行完成時被呼叫;
  • 支援通過 KVO 來觀察 operation 執行狀態的變化;
  • 支援設定執行的優先順序,從而影響 operation 之間的相對執行順序;
  • 支援取消,可以停止正在執行的 operation

併發 vs. 非併發 Operation

一般都是通過將 operation 新增到 operation queue 的方式來執行 operation,但這並不是必須的,還可以直接呼叫 start 方法來執行,但這種方式不能保證 operation 是非同步執行的。Operation 類的 isConcurrent 方法的返回值標識了相對於呼叫 start 方法的執行緒是否非同步執行,預設情況下,isConcurrent 的返回值為 false,即會阻塞呼叫 start 方法的執行緒。

自定義併發執行的 operation,需要編寫一些額外的程式碼支援非同步執行,如建立新執行緒,呼叫系統的非同步方法或其他方式確保 start 方法在開始執行任務後立即返回。但絕大多數情況下,都是將 operation 新增到 operation queue 的方式來執行,因此沒必要實現併發的 operation。

建立 NSInvocationOperation

Swift 不可用,略。

建立 BlockOperation

let blockOperation = BlockOperation(block: {
    print("start excute block1")
    sleep(1)
    print("finish block1")
})
blockOperation.addExecutionBlock {
    print("start excute block2")
    sleep(1)
    print("finish block2")
}
blockOperation.addExecutionBlock {
    print("start excute block3")
    sleep(1)
    print("finish block3")
}
blockOperation.start()複製程式碼
* 非同步執行,blocks 開始與結束的順序不確定

自定義 Operation 物件
  • 非併發 Operation 子類
    • 執行 main 方法中的主任務
    • 響應取消事件:
      • 儘管 operation 是支援取消操作的,但卻並不是立即取消的,而是在你呼叫了 operation 的 cancel 方法之後的下一個 isCancelled 的檢查點取消的。
  • 併發 Operation 子類額外配置:
    • 重寫 start 方法,並且一定不要呼叫父類的實現
    • 重寫 isExecuting 和 isFinished,維護這兩個狀態,並生成響應的 KVO 通知
    • 重寫 isConcurrent,返回 true.

注意! 即使一個 operation 是被 cancel 掉了,仍需手動觸發 isFinished 的 KVO 通知。因為 operation 依賴會觀察其 isFinished 值變化。

維護 KVO 通知

Operation 類以下 Key Path 支援 KVO 通知:
  • isCancelled
  • isConcurrent
  • isExecuting
  • isFinished
  • isReady
  • dependencies
  • queuePriority
  • completionBlock


定製 Operation 物件的行為

Operation 的靈活性就體現在執行行為的可定製
  • 配置(單向)依賴關係
    • addDependency(op:)
    • removeDependency(op:)
    • 與 operation queue 無關
    • 注意!不要迴圈依賴,否則永遠不會執行
    • 在手動執行或新增到 operation queue 之前配置好依賴關係,否則可能會失效
  • 修改 Operation 在佇列中的優先順序
    • .queuePriority
    • 第一要素是 operation 的 isReady 狀態(取決於依賴關係)
    • 其次即 operation 在佇列中的優先順序
    • 預設優先順序 normal
    • 應用於相對的 operation queue
  • 修改 Operation 線上程中的優先順序(iOS 8.0 廢棄,新增 qualityOfService 替代)
    • .threadPriority
    • 預設的 start 方法會修改它的執行緒優先順序
    • 隻影響 main 方法執行所線上程
    • 自定義併發 operation 子類時在 start 方法中也要根據指定的值修改當前執行緒的優先順序
  • 設定 Completion Block
    • .completionBlock
    • 注意!operation 被取消時,completion block 也會執行
    • 無法保證在主執行緒執行

執行 Operation

  • 將 operation 新增到一個 operation queue,自動執行
    • 建立 OperationQueue 物件例項
    • 新增 operation
      • addOperation(op: Operation)
      • addOperations(ops: [Operation], waitUntilFinished: Bool)
      • addOperation(block: )
    • operation 新增到 operation queue 了就不要再修改了
  • 直接呼叫 start 方法手動執行
    • 執行前應該做些防範性判斷
      • isReady
      • isCancel

取消 Operation

operation 加入 operation queue 後,唯一 dequeue 的辦法就是呼叫 operation 的 cancel 方法

等待 Operation 結束

OperationQueue 的 addOperations(ops: [Operation], waitUntilFinished: Bool) 和 Operation 的 waitUntilFinished 方法用於阻塞當前執行緒,直到相關 operation 執行結束。
注意!避免阻塞主執行緒。

暫停和恢復 Operation Queue

暫停佇列並不能暫停正在執行的 operation,只是暫停排程新的 operation

更多參考


相關文章