<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>

您好,歡迎來電子發燒友網! ,新用戶?[免費注冊]

您的位置:電子發燒友網>源碼下載>通訊/手機編程>

Dispatch Queue任務執行與Dispatch Source

大?。?/span>0.5 MB 人氣: 2017-10-11 需要積分:1
 導讀本文為讀《Concurrency Programming Guide》筆記第三篇,在對OS X和iOS應用開發中實現任務異步執行的技術、注意事項、Operation與Dispatch Queues實踐解析后,作者付宇軒(@DevTalking)著重分享了讓Dispatch Queue執行任務的那些事兒。當然,本著“Talk is cheap, show me the code”原則,除卻講解外,作者還分享了各個知識點具體操作實現的Swift代碼片段。
  系列閱讀(文章點擊【閱讀原文】)
  iOS開發中設計并發任務技術與注意事項
  iOS并發編程中Operation與Dispatch Queues實踐
  iOS并發編程指南:Dispatch Queue任務執行與Dispatch Source
  通過Dispatch Queue執行任務
  如果想讓Dispatch Queue執行任務,首先就是得將任務放入隊列中,我們可以異步的將任務加入隊列,也可以同步的將任務加入隊列,可以一個任務一個任務的加,也可以一組一組的加。這節我們就來看看將任務加入隊列的那些事。
  向隊列添加任務
  我們可以使用dispatch_async或者dispatch_async_f函數異步的向隊列中添加任務,也就是說當我們添加完任務后該函數會立即返回,我們不需要等待任務執行完成,而且我們也不會知道隊列到底何時開始執行任務。dispatch_async函數有兩個參數,一個是目標隊列,類型為dispatch_queue_t,另一個是閉包,類型為dispatch_block_t:
  let serialQueue = dispatch_queue_create(“com.example.MySerialQueue”, nil)
  dispatch_async(serialQueue, {
  print(“Task in the queue.。.”)
  })
  dispatch_async_f函數有三個參數,第一個是類型為dispatch_queue_t的目標隊列,第二個是隊列上下文指針,第三個是類型為dispatch_function_t的任務函數,隊列上下文指針為該函數的唯一參數:
  class AddTaskToQueue {
  func launch() {
  let serialQueue = dispatch_queue_create(“com.example.MySerialQueue”, nil)
  dispatch_async(serialQueue, {
  print(“Task in the queue.。.”)
  })
  dispatch_async_f(serialQueue, unsafeBitCast(0, UnsafeMutablePointer《Int》.self), taskFunction())
  sleep(3)
  }
  func taskFunction() -》 dispatch_function_t {
  return { context in
  print(“Do some work with context.。.”)
  }
  }
  }
  let addTaskToQueue = AddTaskToQueue()
  addTaskToQueue.launch()
  除了這兩個函數,我們還可以使用dispatch_sync和dispatch_sync_f函數同步的向隊列中添加任務,并且我們要等待直到任務執行完成。這兩個函數和上面的異步添加任務函數用法完全一致。
  那么什么時候用異步什么時候用同步呢,大多數情況下我們都是在主線程中使用GCD分派任務,為了避免阻塞主線程,影響用戶體驗,所以通常情況下我們都使用異步添加任務的方式。當然為了避免任務與主線程中產生資源競爭的問題,有時候酌情也會使用同步添加任務的方式。
  Dispatch Queue的Completion Block
  還記得NSOperation的completionBlock屬性嗎,這個回調函數在任務執行完成后調用,用于處理有些后續工作或者消息通知。在Dispatch Queue中并沒有類似的屬性,但是我們可以通過其他方式來實現。舉一個很常見的應用場景,我們在主線程中分派一個下載圖片的任務,讓其在二級線程中執行,當圖片下載完成后通知主線程,并由主線程將圖片顯示出來,我們看看簡單的代碼片段:
  class DownloadImage {
  func dispatchTaskInMainThread() {
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), downloadImage())
  }
  func downloadImage() -》 (() -》 ()) {
  return {
  print(“Downloading image in \(NSThread.currentThread())”)
  dispatch_async(dispatch_get_main_queue()) {
  print(“Handle image and display in \(NSThread.currentThread())”)
  }
  }
  }
  }
  我們來看看上面代碼都做了些什么,首先在dispatchTaskInMainThread方法中,我們使用dispatch_get_global_queue函數獲取到全局并發隊列,然后將downloadImage下載圖片的方法作為任務添加到該全局隊列中。在downloadImage方法里,當圖片下載完成后通過dispatch_get_main_queue函數獲取到主隊列,也就是在主線程中對圖片進行處理,這樣我們就達到了Completion Block的效果。
  在隊列中循環執行任務
  在我們的日常開發中,經常會使用到for循環來處理一些任務,而且這些任務之間也并沒有先后順序的關聯,每個任務相對比較獨立。遇到這種情況,我們可以用dispatch_apply或dispatch_apply_f函數讓任務在隊列中循環執行,并且可以是并發執行,這樣相比for循環的串行執行要更加效率:
  // for循環
  let arr = [“Swift”, “Objective-C”, “Java”, “Delphi”, “C++”]
  for element in arr {
  print(“Handle element. the element is \(element)”)
  }
  // dispatch_apply
  dispatch_apply(arr.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){ index in
  print(“Handle element. the element is \(arr[index])。 Current thread is \(NSThread.currentThread())”)
  }
  從上面示例代碼片段中可以看到,dispatch_apply函數有三個參數,第一個參數是循環次數,第二個參數是目標隊列,第三個則是要執行的閉包任務,循環次數是該閉包的唯一參數。
  暫停和重啟隊列
  在Dispatch Queue執行任務時,如果我們想暫停隊列,可以使用dispatch_suspend函數,重新讓隊列執行任務可以使用dispatch_resume。這里要注意的是暫停隊列只是讓隊列暫時停止執行下一個任務,而不是中斷當前正在執行的任務。
  Dispatch Group的使用
  在實際開發中,為了提升性能我們或許會經常使用dispatch_async異步的將任務添加進隊列去執行,但有些時候需要之前隊列中的多個任務都執行完成之后,才能獲取到正確的或者說想要的結果供后續邏輯代碼使用,遇到這種情況,就可以使用Dispatch Group,將多個任務在隊列中歸為一個組,并可以使用dispatch_group_wait函數讓之后的邏輯代碼等待,直到該組的任務都執行完成后再執行。
  var count = 0
  let concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  dispatch_async(concurrentQueue) {
  print(“Task1 in dispatchGroup.。.”)
  sleep(2)
  count += 1
  }
  dispatch_async(concurrentQueue) {
  print(“Task2 in dispatchGroup.。.”)
  sleep(3)
  count += 1
  }
  dispatch_async(concurrentQueue) {
  print(“Task3 in dispatchGroup.。.”)
  sleep(1)
  count += 1
  }
  print(“I expect the count is 3, and the factual count is \(count)”)
  上面的代碼片段就是我剛才描述的場景,因為使用的是并發隊列,也不好在每個任務里進行回調處理,所以我們永遠不會得到正確的count。如果我們使用Dispatch Group事情就簡單多了:
  var count = 0
  let concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  let dispatchGroup = dispatch_group_create()
  dispatch_group_async(dispatchGroup, concurrentQueue) {
  print(“Task1 in dispatchGroup.。.”)
  sleep(2)
  count += 1
  }
  dispatch_group_async(dispatchGroup, concurrentQueue) {
  print(“Task2 in dispatchGroup.。.”)
  sleep(3)
  count += 1
  }
  dispatch_group_async(dispatchGroup, concurrentQueue) {
  print(“Task3 in dispatchGroup.。.”)
  sleep(1)
  count += 1
  }
  dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER)
  print(“I expect the count is 3, and the factual count is \(count)”)
  上面的代碼中,先使用dispatch_group_create函數創建Dispatch Group,然后使用dispatch_group_async函數將任務分進組里,然后再添加進隊列中。該函數有三個參數,分別是Dispatch Group、Dispatch Queue和要執行任務的閉包。當添加完任務后使用dispatch_group_wait函數等待,直到指定組的任務全部完成,才會繼續執行后面的打印語句,該函數有兩個參數,第一個是目標組,第二個是等待時間DISPATCH_TIME_NOW或DISPATCH_TIME_FOREVER。
  Dispatch Source
  前面的文章中介紹過Dispatch Source:
  Dispatch Source是GCD中的一個基本類型,從字面意思可稱為調度源,它的作用是當有一些特定的較底層的系統事件發生時,調度源會捕捉到這些事件,然后可以做其他的邏輯處理,調度源有多種類型,分別監聽對應類型的系統事件。我們來看看它都有哪些類型:
  Timer Dispatch Source:定時調度源。
  Signal Dispatch Source:監聽UNIX信號調度源,比如監聽代表掛起指令的SIGSTOP信號。
  Deor Dispatch Source:監聽文件相關操作和Socket相關操作的調度源。
  Process Dispatch Source:監聽進程相關狀態的調度源。
  Mach port Dispatch Source:監聽Mach相關事件的調度源。
  Custom Dispatch Source:監聽自定義事件的調度源。
  這一節就來看看如何使用Dispatch Source。
  用通俗一點的話說就是用GCD的函數指定一個希望監聽的系統事件類型,再指定一個捕獲到事件后進行邏輯處理的閉包或者函數作為回調函數,然后再指定一個該回調函數執行的Dispatch Queue即可,當監聽到指定的系統事件發生時會調用回調函數,將該回調函數作為一個任務放入指定的隊列中執行。也就是說當監聽到系統事件后就會觸發一個任務,并自動將其加入隊列執行,這里與之前手動添加任務的模式不同,一旦將Diaptach Source與Dispatch Queue關聯后,只要監聽到系統事件,Dispatch Source就會自動將任務(回調函數)添加到關聯的隊列中。
  有些時候回調函數執行的時間較長,在這段時間內Dispatch Source又監聽到多個系統事件,理論上就會形成事件積壓,但好在Dispatch Source有很好的機制解決這個問題,當有多個事件積壓時會根據事件類型,將它們進行關聯和結合,形成一個新的事件。
  監聽事件類型
  Dispatch Source一共可以監聽六類事件,分為11個類型,我們來看看都是什么:
  DISPATCH_SOURCE_TYPE_DATA_ADD:屬于自定義事件,可以通過dispatch_source_get_data函數獲取事件變量數據,在我們自定義的方法中可以調用dispatch_source_merge_data函數向Dispatch Source設置數據,下文中會有詳細的演示。
  DISPATCH_SOURCE_TYPE_DATA_OR:屬于自定義事件,用法同上面的類型一樣。
  DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口發送事件。
  DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
  DISPATCH_SOURCE_TYPE_PROC:與進程相關的事件。
  DISPATCH_SOURCE_TYPE_READ:讀文件事件。
  DISPATCH_SOURCE_TYPE_WRITE:寫文件事件。
  DISPATCH_SOURCE_TYPE_VNODE:文件屬性更改事件。
  DISPATCH_SOURCE_TYPE_SIGNAL:接收信號事件。
  DISPATCH_SOURCE_TYPE_TIMER:定時器事件。
  DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:內存壓力事件。
  創建Dispatch Source
  我們可以使用dispatch_source_create函數創建Dispatch Source,該函數有四個參數:
  type:第一個參數用于標識Dispatch Source要監聽的事件類型,共有11個類型。
  handle:第二個參數是取決于要監聽的事件類型,比如如果是監聽Mach端口相關的事件,那么該參數就是mach_port_t類型的Mach端口號,如果是監聽事件變量數據類型的事件那么該參數就不需要,設置為0就可以了。
  mask:第三個參數同樣取決于要監聽的事件類型,比如如果是監聽文件屬性更改的事件,那么該參數就標識文件的哪個屬性,比如DISPATCH_VNODE_RENAME。
  queue:第四個參數設置回調函數所在的隊列。
  let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueue)
  上面的代碼就是創建Dispatch Source的簡單示例。
  設置事件處理器
  前文中提到過,當Dispatch Source監聽到事件時會調用指定的回調函數或閉包,該回調函數或閉包就是Dispatch Source的事件處理器。我們可以使用dispatch_source_set_event_handler或dispatch_source_set_event_handler_f函數給創建好的Dispatch Source設置處理器,前者是設置閉包形式的處理器,后者是設置函數形式的處理器:
  dispatch_source_set_event_handler(dispatchSource, {
  print(“Dispatch Source 事件處理器。..”)
  })
  // 根據閉包尾隨的特性,還可以有下面的寫法
  dispatch_source_set_event_handler(dispatchSource) {
  print(“Dispatch Source 事件處理器。..”)
  }
  從上面示例代碼中可以看到,該函數有兩個參數,第一個是設置目標Dispatch Source,第二個參數就是設置處理器了。
  既然是事件處理器,那么肯定需要獲取一些Dispatch Source的信息,GCD提供了三個在處理器中獲取Dispatch Source相關信息的函數,比如handle、mask。而且針對不同類型的Dispatch Source,這三個函數返回數據的值和類型都會不一樣,下面來看看這三個函數:
  dispatch_source_get_handle:這個函數用于獲取在創建Dispatch Source時設置的第二個參數handle。
  如果是讀寫文件的Dispatch Source,返回的就是描述符。
  如果是信號類型的Dispatch Source,返回的是int類型的信號數。
  如果是進程類型的Dispatch Source,返回的是pid_t類型的進程id。
  如果是Mach端口類型的Dispatch Source,返回的是mach_port_t類型的Mach端口。
  dispatch_source_get_data:該函數用于獲取Dispatch Source監聽到事件的相關數據。
  如果是讀文件類型的Dispatch Source,返回的是讀到文件內容的字節數。
  如果是寫文件類型的Dispatch Source,返回的是文件是否可寫的標識符,正數表示可寫,負數表示不可寫。
  如果是監聽文件屬性更改類型的Dispatch Source,返回的是監聽到的有更改的文件屬性,用常量表示,比如DISPATCH_VNODE_RENAME等。
  如果是進程類型的Dispatch Source,返回監聽到的進程狀態,用常量表示,比如DISPATCH_PROC_EXIT等。
  如果是Mach端口類型的Dispatch Source,返回Mach端口的狀態,用常量表示,比如DISPATCH_MACH_SEND_DEAD等。
  如果是自定義事件類型的Dispatch Source,返回使用dispatch_source_merge_data函數設置的數據。
  dispatch_source_get_mask:該函數用于獲取在創建Dispatch Source時設置的第三個參數mask。在進程類型,文件屬性更改類型,Mach端口類型的Dispatch Source下該函數返回的結果與dispatch_source_get_data一樣。
  注冊Cancellation Handler
  Cancellation Handler就是當Dispatch Source被釋放時用來處理一些后續事情,比如關閉文件描述符或者釋放Mach端口等。我們可以使用dispatch_source_set_cancel_handler函數或者dispatch_source_set_cancel_handler_f函數給Dispatch Source注冊Cancellation Handler:
  dispatch_source_set_cancel_handler(dispatchSource) {
  print(“進行善后處理。..”)
  }
  該函數有兩個參數,第一個參數是目標Dispatch Source,第二個參數就是要進行善后處理的閉包或者函數。
  更改Dispatch Source的目標隊列
  在上文中,我們說過可以使用dispatch_source_create函數創建Dispatch Source,并且在創建時會指定回調函數執行的隊列,那么如果事后想更改隊列,比如說想更改隊列的優先級,這時我們可以使用dispatch_set_target_queue函數實現:
  let dispatchQueueDefaultPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueueDefaultPriority)
  let dispatchQueueLowPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
  dispatch_set_target_queue(dispatchSource, dispatchQueueLowPriority)
  這里需要注意的是,如果在更改目標隊列時,Dispatch Source已經監聽到相關事件,并且回調函數已經在之前的隊列中執行了,那么會一直在舊的隊列中執行完成,不會轉移到新的隊列中去。
  暫?;謴虳ispatch Source
  暫停和恢復Dispatch Source與Dispatch Queue一樣,都適用dispatch_suspend和dispatch_resume函數。這里需要注意的是剛創建好的Dispatch Source是處于暫停狀態的,所以使用時需要用dispatch_resume函數將其啟動。
  廢除Dispatch Source
  如果我們不再需要使用某個Dispatch Source時,可以使用dispatch_source_cancel函數廢除,該函數只有一個參數,那就是目標Dispatch Source。
  Dispatch Source實踐
  說了這么多,這一節來看看Dispatch Source到底怎么用。
  用Dispatch Source監聽定時器
  Dispatch Source能監聽的事件中有一個類型就是定時器,我們來看看如何實現:
  class TestDispatchSource {
  func launch() {
  let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  let timer = createTimerDispatchSource(dispatch_time(DISPATCH_TIME_NOW, 0), interval: NSEC_PER_SEC * 5, leeway: 0, queue: dispatchQueue) {
  print(“處理定時任務,該任務每5秒執行一次。..”)
  }
  dispatch_resume(timer)
  sleep(30)
  }
  func createTimerDispatchSource(startTime: dispatch_time_t, interval: UInt64, leeway: UInt64, queue: dispatch_queue_t, handler: dispatch_block_t) -》 dispatch_source_t {
  let timerDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
  dispatch_source_set_timer(timerDispatchSource, startTime, interval, leeway)
  dispatch_source_set_event_handler(timerDispatchSource, handler)
  return timerDispatchSource
  }
  }
  上面的代碼示例中一個新的函數dispatch_source_set_timer,該函數的作用就是給監聽事件類型為DISPATCH_SOURCE_TYPE_TIMER的Dispatch Source設置相關屬性,該函數有四個參數:
  source:該參數為目標Dispatch Source,類型為dispatch_source_t.
  start:該參數為定時器的起始時間,類型為dispatch_time_t。
  interval:該參數為定時器的間隔時間,類型為UInt64,間隔時間的單位是納秒。
  leeway:該參數為間隔時間的精度,類型為UInt64,時間單位也是納秒。
  用Dispatch Source監聽自定義事件
  Dispatch Source能監聽的事件中有一個類型是自定義事件,下面我們來看看如何使用:
  class TestDispatchSource {
  func launch() {
  var totalProcess = 0
  let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue())
  dispatch_source_set_event_handler(dispatchSource) {
  let process = dispatch_source_get_data(dispatchSource)
  totalProcess += Int(process)
  print(“這里可以在主線程更新UI,顯示進度條。..進度為\(totalProcess)%”)
  }
  dispatch_resume(dispatchSource)
  generateCustomEvent(dispatchSource)
  }
  func generateCustomEvent(dispatchSource: dispatch_source_t) {
  let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  for index in 0.。.100 {
  dispatch_sync(queue) {
  print(“模擬自定義事件。..進度為\(index)%”)
  dispatch_source_merge_data(dispatchSource, 1)
  sleep(2)
  }
  }
  }
  }
  我們來看看generateCustomEvent(dispatchSource: dispatch_source_t)方法,該方法的作用的是模擬自定義事件,首先創建一個全局并發隊列,然后循環讓其執行任務,在執行的任務里調用dispatch_source_merge_data函數,就可以觸發監聽類型為DISPATCH_SOURCE_TYPE_DATA_ADD或者DISPATCH_SOURCE_TYPE_DATA_OR的Dispatch Source。該函數有兩個參數,第一個參數是目標Dispatch Source,第二個參數的類型是無符號長整型,用于向目標Dispatch Source中的對應變量追加指定的數。
  我們再來看看如何監聽自定義時間,首先創建類型為DISPATCH_SOURCE_TYPE_DATA_ADD的Dispatch Source,然后設置回調閉包,在閉包中使用dispatch_source_get_data獲取追加的變量值,該函數只有一個參數,就是目標Dispatch Source,這里需要注意的是通過dispatch_source_get_data函數獲取的變量值并不是累加值,而是每次調用dispatch_source_merge_data函數時設置的值,所以在上面的示例中用totalProcess變量累加每次獲取到的值。
  上面的示例可以用來模擬后臺進行下載,根據下載的數據量使用dispatch_source_merge_data函數給目標Dispatch Source設置相應的變量值,然后在主線程中監聽到Dispatch Source的自定義事件,通過dispatch_source_get_data函數獲取到變量,用于更新顯示進度條的UI。
?

非常好我支持^.^

(0) 0%

不好我反對

(0) 0%

Dispatch Queue任務執行與Dispatch Source下載

相關電子資料下載

      發表評論

      用戶評論
      評價:好評中評差評

      發表評論,獲取積分! 請遵守相關規定!

      ?
      亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
      <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
      <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
      <acronym id="s8ci2"></acronym>
      <acronym id="s8ci2"><center id="s8ci2"></center></acronym>