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

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

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

iOS并發編程中Dispatch Queues的創建與管理的實踐總結

大?。?/span>0.2 MB 人氣: 2017-10-11 需要積分:1
 導讀:本文為讀《Concurrency Programming Guide》筆記第二篇,在上篇分享了OS X和iOS應用開發中實現任務異步執行的技術以及應注意的事項之后,作者付宇軒(@DevTalking)對Operation對象的設置與執行,以及Dispatch Queues的創建與管理進行了實踐總結。
  系列閱讀
  Operation對象的相關設置
  Operation對象除了上文中講到到基本使用方法外還有一些其他的特性,這些特性需要根據我們的應用場景去設置,設置的時機在創建Operation對象之后和運行它或者將其放入操作隊列之前,下面讓我們來看看Operation對象還有哪些特性。
  Operation對象之間的依賴
  與GCD不同,Operation Queue不遵循先進先出的原則,而且Operation Queue始終是并發執行Operation對象的,所以想讓Operation對象串行執行就需要用它的Operation對象依賴特性,該特性可以讓Operation對象將自己與另外一個Operation對象進行關聯,并且當關聯的Operation對象執行完成后才可以執行,這樣就達到了串行執行Operation對象的目的。
  我們可以用NSOperation的addDependency方法添加依賴的Operation對象,而且產生依賴的這兩個Operation對象并不要求必須在相同的操作隊列中,但是這種依賴只能是單向的,不能相互依賴。
  importFoundation classTestOperationDependency{func launch() { letblockOperationA = NSBlockOperation(block: { print(“Task in blockOperationA.。.”) sleep(3) }) letblockOperationB = NSBlockOperation(block: { print(“Task in blockOperationB.。.”) sleep(5) }) blockOperationA.addDependency(blockOperationB) letoperationQueue = NSOperationQueue() operationQueue.addOperation(blockOperationA) operationQueue.addOperation(blockOperationB) sleep(10) } } lettestOperationDependency = TestOperationDependency() testOperationDependency.launch()
  上面的示例代碼展示了如何給Operation對象添加依賴,大家可以注釋掉blockOperationA.addDependency(blockOperationB)這一行看看打印結果有什么區別。
  Operation對象的優先級
  上文中說了,操作隊列里的Operation對象都是并發執行的,如果一個操作隊列中有多個Operation對象,那么誰先執行誰后執行取決于Operation對象的依賴Operation對象是否已執行完成,也就是是否處于準備執行的狀態。其實Operation對象自身也有優先級的屬性,如果有兩個都處于準備執行狀態的Operation對象,那么優先級高的會先執行,優先級低的后執行。每個Operation對象默認的優先級是NSOperationQueuePriority.Normal級別,我們可以通過設置queuePriority屬性更改Operation的在隊列中執行的優先級,優先級別有以下五種:
  NSOperationQueuePriority.Normal:正常優先級NSOperationQueuePriority.Low:低優先級NSOperationQueuePriority.VeryLow:非常低優先級NSOperationQueuePriority.High:高優先級NSOperationQueuePriority.VeryHigh:非常高優先級
  這里我們需要注意一下Operation對象優先級的作用域,它只能作用于相同的操作隊列中,不同操作隊列中的Operation對象是不受優先級影響的。另外需要注意的是,如果有兩個Operation對象,一個處于準備執行狀態,但優先級比較低,另一個處于等待狀態,但優先級比較高,那么此時仍然是處于準備執行狀態的低優先級Operation對象先執行??梢奜peration對象的優先級相互影響需要滿足兩個條件,一是必須處在同一個操作隊列中,另一個是Operation對象都處于準備執行狀態。
  通過Operation對象修改線程優先級
  通常情況下,線程的優先級由內核自己管理,不過在OS X v10.6及以后的版本和iOS4到iOS7期間,NSOperation多了一個threadPriority屬性,我們可以通過該屬性設置Operation對象運行所在線程的優先級,數值范圍為0.0到1.0,數字越高優先級越高。不過可能是出于線程安全等方面的考慮,Apple從iOS8開始廢除了該屬性。
  設置Completion Block
  上篇文章中說過,Operation對象其中的一個特別好的特性就是完成時回調閉包Completion Block。它的作用不言而喻,就是當主要任務執行完成之后做一些收尾的處理工作,我們可以設置completionBlock屬性給Operation對象添加完成時回調閉包:
  blockOperationA.completionBlock = { print(“blockOperationA has finished.。.”) }
  執行Operation對象
  雖然前面文章的示例中已經包含了對Operation對象的執行,但是并沒詳細說明,這節就說說Operation對象的執行。
  使用Operation Queue
  使用Operation Queue操作隊列執行Operation對象已然是標配選項了,操作隊列在Cocoa框架中對應的類是NSOperationQueue,一個操作隊列中可以添加多個Operation對象,但一次到底添加多少Operation對象得根據實際情況而定,比如應用程序對內存的消耗情況、內核的空閑情況等,所以說凡事得有度,不然反而會適得其反。另外需要注意的一點是不論有多少個操作隊列,它們都受制于系統的負載、內核空閑等運行情況,所以說并不是說再創建一個操作隊列就能執行更多的Operation對象。
  在使用操作隊列時,我們首先要創建NSOperationQueue的實例:
  letoperationQueue = NSOperationQueue()
  然后通過NSOperationQueue的addOperation方法添加Operation對象:
  operationQueue.addOperation(blockOperationA) operationQueue.addOperation(blockOperationB)
  在OS X v10.6之后和iOS4之后,我們還可以用addOperations:waitUntilFinished:方法添加一組Operation對象:
  operationQueue.addOperations([blockOperationA, blockOperationB], waitUntilFinished: false)
  該方法有兩個參數
  ops: [NSOperation]:Operation對象數組。waitUntilFinished wait: Bool:該參數標示這個操作隊列在執行Operation對象時是否會阻塞當前線程。
  我們還可以通過addOperationWithBlock方法向操作隊列中直接添加閉包,而不需要去創建Operation對象:
  operationQueue.addOperationWithBlock({ print(“The block is running in Operation Queue.。.”) })
  除了以上這幾種添加Operation對象的方法外,還可以通過NSOperationQueue的maxConcurrentOperationCount屬性設置同時執行Operation對象的最大數:
  operationQueue.maxConcurrentOperationCount =2
  如果設置為1,那么不管該操作隊列中添加了多少Operation對象,每次都只運行一個,而且會按照添加Operation對象的順序去執行。所以如果遇到添加到操作的隊列的Operation對象延遲執行了,那么通常會有兩個原因:
  添加的Operation對象數超過了操作隊列設置的同時執行Operation對象的最大數。延遲執行的Operation對象在等待它依賴的Operation對象執行完成。
  另外需要的注意的是當Operation對象添加到操作隊列中后,不要再更改它任務中涉及到的任何屬性或者它的依賴,因為到操作隊列中的Operation對象隨時會被執行,所以如果你自以為它還沒有被執行而去修改它,可能并不會達到你想要的結果。
  手動執行Operation對象
  除了用操作隊列來執行Operation對象以外,我們還可以手動執行某個Operation對象,但是這需要我們注意更多的細節問題,也要寫更多的代碼去確保Operation對象能正確執行。在上篇文章中,我們創建過自定義的Operation對象,其中我們知道有幾個屬性特別需要我們注意,那就是ready、concurrent、executing、finished、cancelled,對應Operation對象是否出于準備執行狀態、是否為異步并發執行的、是否正在執行、是否已經執行完成、是否已被終止。
  這些狀態在我們使用操作隊列時都不需要理會,都有操作隊列幫我們把控判斷,確保Operation對象的正確執行,我們只需要在必要的時候獲取狀態信息查看而已。但是如果手動執行Operation對象,那么這些狀態都需要我們來把控,因為你手動執行一個Operation對象時要判斷它的依賴對象是否執行完成,是否被終止了等等,所以并不是簡單的調用start方法,下面來看看如果正確的手動執行Operation對象:
  func performOperation(operation: NSOperation)-》Bool { varresult = falseifoperation.ready && !operation.cancelled { ifoperation.concurrent { operation.start() } else{ NSThread.detachNewThreadSelector(“start”, toTarget: operation, withObject: nil) } result = true} returnresult }
  終止Operation對象執行
  一旦Operation對象被添加到操作隊列中,這個Operation對象就屬于這個操作隊列了,并且不能被移除,唯一能讓Operation對象失效的方法就是通過NSOperation的cancel方法終止它執行,或者也可以通過NSOperationQueue的cancelAllOperations方法終止在隊列中的所有Operation對象。
  暫停和恢復操作隊列
  在實際運用中,如果我們希望暫停操作隊列執行Operation對象,可以通過設置NSOperationQueue的suspended屬性為false來實現,不過這里要注意的是暫停操作隊列只是暫停執行下一個Operation對象,而不是暫停當前正在執行的Operation對象,將suspended屬性設置為true后,操作隊列則恢復執行。
  Dispatch Queues
  Dispatch Queue是GCD中的核心功能,它能讓我們很方便的異步或同步執行任何被封裝為閉包的任務,它的運作模式與Operation Queue很相似,但是有一點不同的是Dispatch Queue是一種先進先出的數據結構,也就是執行任務的順序永遠等同于添加任務時的順序。GCD中已經為我們提供了幾種類型的Dispatch Queue,當然我們也可以根據需求自己創建Dispatch Queue,下面我們先來看看Dispatch Queue的類型:
  串行Dispatch Queue:該類型的隊列一次只能執行一個任務,當前任務完成之后才能執行下一個任務,而且可依任務的不同而在不同的線程中執行,這類隊列通常作為私有隊列使用。這里需要注意的是雖然該類型的隊列一次只能執行一個任務,但是可以讓多個串行隊列同時開始執行任務,達到并發執行的任務的目的。并行Dispatch Queue:該類隊列可同時執行多個任務,但是執行任務的順序依然是遵循先進先出的原則,同樣可依任務的不同而在不同的線程中執行,這類隊列通常作為全局隊列使用。主Dispatch Queue:該類隊列實質上也是一個串行隊列,但是該隊列是一個全局隊列,在該隊列中執行的任務都是在當前應用的主線程中執行的。通常情況下我們不需要自己創建此類隊列。
  Dispatch Queue與Operation Queue相似,都能讓我們更方便的實現并發任務的編程工作,并且能提供更優的性能,因為我們不再需要編寫關于線程管理相關的一大堆代碼,這些完全都有系統接管,我們只需要將注意力放在要執行的任務即可。舉個簡單的例子,如果有兩個任務需要在不同的線程中執行,但是他們之間存在資源競爭的情況,所以需要保證執行的先后順序,如果我們自己創建線程實現該場景,那么就務必要用的線程鎖機制,確保任務有正確的執行順序,這勢必對系統資源的開銷會非常大,如果使用Dispatch Queue,我們只需要將任務安正確的順序添加到串行隊列中即可,省時省力省資源。
  任務的載體是閉包
  在使用Dispatch Queue時,需要將任務封裝為閉包。閉包就是一個函數,或者一個指向函數的指針,加上這個函數執行的非局部變量,閉包最大的一個特性就是可以訪問父作用域中的局部變量。我們在將任務封裝為閉包進行使用時要注意以下這幾點:
  雖然在閉包中可以使用父作用域中的變量,但是盡可能少的使用父作用域中比較大的變量以及不要在閉包中做類似刪除清空父作用域中變量的行為。當將一個封裝好任務的閉包添加至Dispatch Qeueu中,Dispatch Queue會自動復制該閉包,并且在執行完成后釋放該閉包,所以不同擔心閉包中一些值的變化問題,以及資源釋放問題。雖然使用Dispatch Queue執行并發異步任務很方便,但是創建和執行閉包還是有一定資源開銷的,所以盡量不要使用Dispatch Queue執行一些很小的任務,要物有所值。如果確實有很小的任務需要并發異步執行,那么使用NSThread的detachNewThreadSelector方法或NSObject的performSelectorInBackground方法去執行也未必不可。如果同一個隊列中的多個任務之間需要共享數據,那么應該使用隊列上下文去存儲數據,供不同的任務訪問。如果閉包中的任務創建了不少對象,那么應該考慮將整個任務邏輯代碼放在autoreleasepool中,雖然Dispatch Queue中也有自動釋放池,但是你不能保證它每次釋放的時間,所以咱們自己再加一個要來的更保險一些。
  創建與管理Dispatch Queues
  在使用Dispatch Queue之前,我們首先需要考慮應該創建什么類型的Dispatch Queue,如何進行配置等,這一節就來說一說如何創建和管理Dispatch Queue。
  全局并發Dispatch Queue
  并發隊列的好處人人皆知,可以方便的同時處理多個任務,在GCD中并發Dispatch Queue同樣遵循先進先出的原則,但這只是在運行時適用,如果有個任務在并發隊列中還沒輪到它執行,那么此時完全可以移除它,而不必等它前面的任務執行完成之后。至于并發隊列中沒次有多少個任務在執行,這個恐怖在每一秒都在變化,因為影響它的因素有很多,所以之前說過,盡量不要移除移除已經添加進隊列的任務。
  OS X和iOS系統為我們提供了四種全局并發Dispatch Queue,所謂全局隊列,就是我們不需要理會它們的保留和釋放問題,而且不需要專門創建它。與其說是四種不如說是一種全局并發隊列的四種不同優先級,因為它們之間唯一的不同之處就是隊列優先級不同。與Operation Queue不同,在GCD中,Dispatch Queue只有四種優先級:
  DISPATCH_QUEUE_PRIORITY_HIGH:高優先級。DISPATCH_QUEUE_PRIORITY_DEFAULT:默認優先級,低于高優先級。DISPATCH_QUEUE_PRIORITY_LOW:低優先級,低于高優先級和默認優先級。DISPATCH_QUEUE_PRIORITY_BACKGROUND:后臺優先級,低于高優先級和后臺線程執行的任務。
  我們可以通過dispatch_get_global_queue函數再根據不同的優先級獲取不同的全局并發隊列,類型為dispatch_queue_t:
  lethighPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) letdefaultPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  我們在使用全局并發隊列的時候不需要保留隊列的引用,隨時要用隨時用該函數獲取即可。當然我們也可以通過dispatch_queue_create函數自己創建隊列:
  letconcurrentQueue = dispatch_queue_create(“com.example.MyConcurrentQueue”, DISPATCH_QUEUE_CONCURRENT)
  從上面代碼可以看到,dispatch_queue_create函數有兩個參數,第一個為隊列的名稱,第二個為隊列類型,串行隊列為DISPATCH_QUEUE_SERIAL,并發隊列為DISPATCH_QUEUE_CONCURRENT。
  串行Dispatch Queue
  串行隊列可以讓我們將任務按照一定順序執行,能更優的處理多個任務之間的資源競爭問題,比線程鎖機制有更小的資源開銷和更好的性能,并且不會產生死鎖的問題。
  系統也為我們提供了一個串行隊列,我們可以通過dispatch_get_main_queue函數獲?。?br />   letmainQueue = dispatch_get_main_queue()
  該隊列與當前應用的主線程相關聯。當然我們也可以自己創建串行隊列:
  letserialQueueA = dispatch_queue_create(“com.example.MySerialQueueA”, DISPATCH_QUEUE_SERIAL) // 或者letserialQueueB = dispatch_queue_create(“com.example.MySerialQueueB”, nil)
  dispatch_queue_create函數的第二個參數如果為nil則默認創建串行隊列。當我們創建好串行隊列后,系統會自動將創建好的隊列與當前應用的主線程進行關聯。
  獲取當前隊列
  如果需要驗證或者測試當前隊列,我們可以通過dispatch_get_current_queue函數獲取當前隊列。如果在閉包中調用,返回的是該閉包所在的隊列,如果在閉包外調用,返回的則是默認的并發隊列。不過該函數在OS X v10.10中和Swift中都不能使用了,取而代之的是通過DISPATCH_CURRENT_QUEUE_LABEL屬性的get方法。
  擅用隊列上下文
  很多情況下,同一個隊列中的不同任務之間需要共享數據,尤其像串行隊列中的任務,可能由多個任務對某個變量進行處理,或者都需要使用到某個對象,這時就要用到隊列上下文:
  import Foundation classTestDispatchQueue{func launch() { let serialQueue = dispatch_queue_create(“com.example.MySerialQueue”, DISPATCH_QUEUE_SERIAL) dispatch_set_context(serialQueue, unsafeBitCast(0, UnsafeMutablePointer《Int》.self)) dispatch_async(serialQueue, { vartaskCount = unsafeBitCast(dispatch_get_context(serialQueue), Int.self) taskCount++ print(“TaskA in the dispatch queue.。.and The number of task in queue is \(taskCount)”) dispatch_set_context(serialQueue, unsafeBitCast(taskCount, UnsafeMutablePointer《Int》.self)) sleep(1) }) dispatch_async(serialQueue, { vartaskCount = unsafeBitCast(dispatch_get_context(serialQueue), Int.self) taskCount++ print(“TaskB in the dispatch queue.。.and The number of task in queue is \(taskCount)”) dispatch_set_context(serialQueue, unsafeBitCast(taskCount, UnsafeMutablePointer《Int》.self)) }) sleep(3) } } let testDispatchQueue = TestDispatchQueue() testDispatchQueue.launch()
  從上面的代碼示例中可以看到,在執行代碼點,我們用dispatch_set_context函數向serialQueue隊列的上下文環境中設置了一個Int類型的變量,初始值為0。該函數有兩個參數,第一個是目標隊列,第二個參數是上下文數據的指針。然后在閉包中我們使用dispatch_get_context函數獲取上下文數據進行進一步的處理。除了基本類型,我們也可以將自定義的類放入隊列上下文中:
  importFoundation class Contact: NSObject { letname =“DevTalking”letmobile =“10010”} class TestDispatchQueue { letcontact =Contact() func launch() { letserialQueue =dispatch_queue_create(“com.example.MySerialQueue”, DISPATCH_QUEUE_SERIAL) dispatch_set_context(serialQueue, unsafeBitCast(contact, UnsafeMutablePointer《Void》.self)) dispatch_async(serialQueue, { letcontact =unsafeBitCast(dispatch_get_context(serialQueue), Contact.self) print(“The name is \(contact.name)”) sleep(1) }) dispatch_async(serialQueue, { letcontact =unsafeBitCast(dispatch_get_context(serialQueue), Contact.self) print(“The name is \(contact.mobile)”) }) sleep(3) } } lettestDispatchQueue =TestDispatchQueue() testDispatchQueue.launch()
  關于unsafeBitCast函數和Swift中指針的用法在這里可以有所參考。
  隊列的收尾工作
  雖然在ARC時代,資源釋放的工作已經基本不需要我們手動去做了,但有些時候因為系統釋放資源并不是很及時,也會造成內存移除等問題,所以在一些情況下我們還是需要進行手動釋放資源的工作,必入添加autoreleasepool保證資源及時釋放等。Dispatch Queue也給我們提供了這樣的機會(機會針對于ARC時代,在MRC時代是必須要做的),那就是Clean Up Function清理掃尾函數,當隊列被釋放時,或者說引用計數為0時會調用該函數,并且將上下文指針也傳到了該函數,以便進行清理工作:
  importFoundation class Contact: NSObject { letname =“DevTalking”letmobile =“10010”} class TestDispatchQueue { letcontact =Contact() func testCleanUpFunction() { launch() sleep(15) } func launch() { letserialQueue =dispatch_queue_create(“com.example.MySerialQueue”, DISPATCH_QUEUE_SERIAL) dispatch_set_context(serialQueue, unsafeBitCast(contact, UnsafeMutablePointer《Void》.self)) dispatch_set_finalizer_f(serialQueue, myFinalizerFunction()) dispatch_async(serialQueue, { letcontact =unsafeBitCast(dispatch_get_context(serialQueue), Contact.self) print(“The name is \(contact.name)”) sleep(1) }) dispatch_async(serialQueue, { letcontact =unsafeBitCast(dispatch_get_context(serialQueue), Contact.self) print(“The name is \(contact.mobile)”) }) sleep(3) } func myFinalizerFunction() -》 dispatch_function_t { return{ context inletcontact =unsafeBitCast(context, Contact.self) print(“The name is \(contact.name) and the mobile is \(contact.mobile), The serialQueue has been released and we need clean up context data.”) // TODO.。. } } } lettestDispatchQueue =TestDispatchQueue() testDispatchQueue.testCleanUpFunction()
  從上面的代碼示例中可以看到當給隊列設置完上下文時,我們使用了dispatch_set_finalizer_f函數給隊列設置清理函數,dispatch_set_finalizer_f函數有兩個參數,第一個是目標隊列,第二個參數是類型為dispatch_function_t的函數指針,也就是清理函數,上下文數據指針是該函數唯一的參數。在上面代碼中,我們添加了myFinalizerFunction函數作為清理函數,在該函數中獲得上下文數據,然后進行后續的清理工作。
?

非常好我支持^.^

(0) 0%

不好我反對

(0) 0%

      發表評論

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

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

      ?
      亚洲欧美日韩精品久久_久久精品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>