iOS并發(fā)編程中Dispatch Queues的創(chuàng)建與管理的實踐總結
大?。?/span>0.2 MB 人氣: 2017-10-11 需要積分:1
標簽:iOS(148619)
導讀:本文為讀《Concurrency Programming Guide》筆記第二篇,在上篇分享了OS X和iOS應用開發(fā)中實現任務異步執(zhí)行的技術以及應注意的事項之后,作者付宇軒(@DevTalking)對Operation對象的設置與執(zhí)行,以及Dispatch Queues的創(chuàng)建與管理進行了實踐總結。系列閱讀
Operation對象的相關設置
Operation對象除了上文中講到到基本使用方法外還有一些其他的特性,這些特性需要根據我們的應用場景去設置,設置的時機在創(chuàng)建Operation對象之后和運行它或者將其放入操作隊列之前,下面讓我們來看看Operation對象還有哪些特性。
Operation對象之間的依賴
與GCD不同,Operation Queue不遵循先進先出的原則,而且Operation Queue始終是并發(fā)執(zhí)行Operation對象的,所以想讓Operation對象串行執(zhí)行就需要用它的Operation對象依賴特性,該特性可以讓Operation對象將自己與另外一個Operation對象進行關聯,并且當關聯的Operation對象執(zhí)行完成后才可以執(zhí)行,這樣就達到了串行執(zhí)行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)這一行看看打印結果有什么區(qū)別。
Operation對象的優(yōu)先級
上文中說了,操作隊列里的Operation對象都是并發(fā)執(zhí)行的,如果一個操作隊列中有多個Operation對象,那么誰先執(zhí)行誰后執(zhí)行取決于Operation對象的依賴Operation對象是否已執(zhí)行完成,也就是是否處于準備執(zhí)行的狀態(tài)。其實Operation對象自身也有優(yōu)先級的屬性,如果有兩個都處于準備執(zhí)行狀態(tài)的Operation對象,那么優(yōu)先級高的會先執(zhí)行,優(yōu)先級低的后執(zhí)行。每個Operation對象默認的優(yōu)先級是NSOperationQueuePriority.Normal級別,我們可以通過設置queuePriority屬性更改Operation的在隊列中執(zhí)行的優(yōu)先級,優(yōu)先級別有以下五種:
NSOperationQueuePriority.Normal:正常優(yōu)先級NSOperationQueuePriority.Low:低優(yōu)先級NSOperationQueuePriority.VeryLow:非常低優(yōu)先級NSOperationQueuePriority.High:高優(yōu)先級NSOperationQueuePriority.VeryHigh:非常高優(yōu)先級
這里我們需要注意一下Operation對象優(yōu)先級的作用域,它只能作用于相同的操作隊列中,不同操作隊列中的Operation對象是不受優(yōu)先級影響的。另外需要注意的是,如果有兩個Operation對象,一個處于準備執(zhí)行狀態(tài),但優(yōu)先級比較低,另一個處于等待狀態(tài),但優(yōu)先級比較高,那么此時仍然是處于準備執(zhí)行狀態(tài)的低優(yōu)先級Operation對象先執(zhí)行??梢奜peration對象的優(yōu)先級相互影響需要滿足兩個條件,一是必須處在同一個操作隊列中,另一個是Operation對象都處于準備執(zhí)行狀態(tài)。
通過Operation對象修改線程優(yōu)先級
通常情況下,線程的優(yōu)先級由內核自己管理,不過在OS X v10.6及以后的版本和iOS4到iOS7期間,NSOperation多了一個threadPriority屬性,我們可以通過該屬性設置Operation對象運行所在線程的優(yōu)先級,數值范圍為0.0到1.0,數字越高優(yōu)先級越高。不過可能是出于線程安全等方面的考慮,Apple從iOS8開始廢除了該屬性。
設置Completion Block
上篇文章中說過,Operation對象其中的一個特別好的特性就是完成時回調閉包Completion Block。它的作用不言而喻,就是當主要任務執(zhí)行完成之后做一些收尾的處理工作,我們可以設置completionBlock屬性給Operation對象添加完成時回調閉包:
blockOperationA.completionBlock = { print(“blockOperationA has finished.。.”) }
執(zhí)行Operation對象
雖然前面文章的示例中已經包含了對Operation對象的執(zhí)行,但是并沒詳細說明,這節(jié)就說說Operation對象的執(zhí)行。
使用Operation Queue
使用Operation Queue操作隊列執(zhí)行Operation對象已然是標配選項了,操作隊列在Cocoa框架中對應的類是NSOperationQueue,一個操作隊列中可以添加多個Operation對象,但一次到底添加多少Operation對象得根據實際情況而定,比如應用程序對內存的消耗情況、內核的空閑情況等,所以說凡事得有度,不然反而會適得其反。另外需要注意的一點是不論有多少個操作隊列,它們都受制于系統(tǒng)的負載、內核空閑等運行情況,所以說并不是說再創(chuàng)建一個操作隊列就能執(zhí)行更多的Operation對象。
在使用操作隊列時,我們首先要創(chuàng)建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:該參數標示這個操作隊列在執(zhí)行Operation對象時是否會阻塞當前線程。
我們還可以通過addOperationWithBlock方法向操作隊列中直接添加閉包,而不需要去創(chuàng)建Operation對象:
operationQueue.addOperationWithBlock({ print(“The block is running in Operation Queue.。.”) })
除了以上這幾種添加Operation對象的方法外,還可以通過NSOperationQueue的maxConcurrentOperationCount屬性設置同時執(zhí)行Operation對象的最大數:
operationQueue.maxConcurrentOperationCount =2
如果設置為1,那么不管該操作隊列中添加了多少Operation對象,每次都只運行一個,而且會按照添加Operation對象的順序去執(zhí)行。所以如果遇到添加到操作的隊列的Operation對象延遲執(zhí)行了,那么通常會有兩個原因:
添加的Operation對象數超過了操作隊列設置的同時執(zhí)行Operation對象的最大數。延遲執(zhí)行的Operation對象在等待它依賴的Operation對象執(zhí)行完成。
另外需要的注意的是當Operation對象添加到操作隊列中后,不要再更改它任務中涉及到的任何屬性或者它的依賴,因為到操作隊列中的Operation對象隨時會被執(zhí)行,所以如果你自以為它還沒有被執(zhí)行而去修改它,可能并不會達到你想要的結果。
手動執(zhí)行Operation對象
除了用操作隊列來執(zhí)行Operation對象以外,我們還可以手動執(zhí)行某個Operation對象,但是這需要我們注意更多的細節(jié)問題,也要寫更多的代碼去確保Operation對象能正確執(zhí)行。在上篇文章中,我們創(chuàng)建過自定義的Operation對象,其中我們知道有幾個屬性特別需要我們注意,那就是ready、concurrent、executing、finished、cancelled,對應Operation對象是否出于準備執(zhí)行狀態(tài)、是否為異步并發(fā)執(zhí)行的、是否正在執(zhí)行、是否已經執(zhí)行完成、是否已被終止。
這些狀態(tài)在我們使用操作隊列時都不需要理會,都有操作隊列幫我們把控判斷,確保Operation對象的正確執(zhí)行,我們只需要在必要的時候獲取狀態(tài)信息查看而已。但是如果手動執(zhí)行Operation對象,那么這些狀態(tài)都需要我們來把控,因為你手動執(zhí)行一個Operation對象時要判斷它的依賴對象是否執(zhí)行完成,是否被終止了等等,所以并不是簡單的調用start方法,下面來看看如果正確的手動執(zhí)行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對象執(zhí)行
一旦Operation對象被添加到操作隊列中,這個Operation對象就屬于這個操作隊列了,并且不能被移除,唯一能讓Operation對象失效的方法就是通過NSOperation的cancel方法終止它執(zhí)行,或者也可以通過NSOperationQueue的cancelAllOperations方法終止在隊列中的所有Operation對象。
暫停和恢復操作隊列
在實際運用中,如果我們希望暫停操作隊列執(zhí)行Operation對象,可以通過設置NSOperationQueue的suspended屬性為false來實現,不過這里要注意的是暫停操作隊列只是暫停執(zhí)行下一個Operation對象,而不是暫停當前正在執(zhí)行的Operation對象,將suspended屬性設置為true后,操作隊列則恢復執(zhí)行。
Dispatch Queues
Dispatch Queue是GCD中的核心功能,它能讓我們很方便的異步或同步執(zhí)行任何被封裝為閉包的任務,它的運作模式與Operation Queue很相似,但是有一點不同的是Dispatch Queue是一種先進先出的數據結構,也就是執(zhí)行任務的順序永遠等同于添加任務時的順序。GCD中已經為我們提供了幾種類型的Dispatch Queue,當然我們也可以根據需求自己創(chuàng)建Dispatch Queue,下面我們先來看看Dispatch Queue的類型:
串行Dispatch Queue:該類型的隊列一次只能執(zhí)行一個任務,當前任務完成之后才能執(zhí)行下一個任務,而且可依任務的不同而在不同的線程中執(zhí)行,這類隊列通常作為私有隊列使用。這里需要注意的是雖然該類型的隊列一次只能執(zhí)行一個任務,但是可以讓多個串行隊列同時開始執(zhí)行任務,達到并發(fā)執(zhí)行的任務的目的。并行Dispatch Queue:該類隊列可同時執(zhí)行多個任務,但是執(zhí)行任務的順序依然是遵循先進先出的原則,同樣可依任務的不同而在不同的線程中執(zhí)行,這類隊列通常作為全局隊列使用。主Dispatch Queue:該類隊列實質上也是一個串行隊列,但是該隊列是一個全局隊列,在該隊列中執(zhí)行的任務都是在當前應用的主線程中執(zhí)行的。通常情況下我們不需要自己創(chuàng)建此類隊列。
Dispatch Queue與Operation Queue相似,都能讓我們更方便的實現并發(fā)任務的編程工作,并且能提供更優(yōu)的性能,因為我們不再需要編寫關于線程管理相關的一大堆代碼,這些完全都有系統(tǒng)接管,我們只需要將注意力放在要執(zhí)行的任務即可。舉個簡單的例子,如果有兩個任務需要在不同的線程中執(zhí)行,但是他們之間存在資源競爭的情況,所以需要保證執(zhí)行的先后順序,如果我們自己創(chuàng)建線程實現該場景,那么就務必要用的線程鎖機制,確保任務有正確的執(zhí)行順序,這勢必對系統(tǒng)資源的開銷會非常大,如果使用Dispatch Queue,我們只需要將任務安正確的順序添加到串行隊列中即可,省時省力省資源。
任務的載體是閉包
在使用Dispatch Queue時,需要將任務封裝為閉包。閉包就是一個函數,或者一個指向函數的指針,加上這個函數執(zhí)行的非局部變量,閉包最大的一個特性就是可以訪問父作用域中的局部變量。我們在將任務封裝為閉包進行使用時要注意以下這幾點:
雖然在閉包中可以使用父作用域中的變量,但是盡可能少的使用父作用域中比較大的變量以及不要在閉包中做類似刪除清空父作用域中變量的行為。當將一個封裝好任務的閉包添加至Dispatch Qeueu中,Dispatch Queue會自動復制該閉包,并且在執(zhí)行完成后釋放該閉包,所以不同擔心閉包中一些值的變化問題,以及資源釋放問題。雖然使用Dispatch Queue執(zhí)行并發(fā)異步任務很方便,但是創(chuàng)建和執(zhí)行閉包還是有一定資源開銷的,所以盡量不要使用Dispatch Queue執(zhí)行一些很小的任務,要物有所值。如果確實有很小的任務需要并發(fā)異步執(zhí)行,那么使用NSThread的detachNewThreadSelector方法或NSObject的performSelectorInBackground方法去執(zhí)行也未必不可。如果同一個隊列中的多個任務之間需要共享數據,那么應該使用隊列上下文去存儲數據,供不同的任務訪問。如果閉包中的任務創(chuàng)建了不少對象,那么應該考慮將整個任務邏輯代碼放在autoreleasepool中,雖然Dispatch Queue中也有自動釋放池,但是你不能保證它每次釋放的時間,所以咱們自己再加一個要來的更保險一些。
創(chuàng)建與管理Dispatch Queues
在使用Dispatch Queue之前,我們首先需要考慮應該創(chuàng)建什么類型的Dispatch Queue,如何進行配置等,這一節(jié)就來說一說如何創(chuàng)建和管理Dispatch Queue。
全局并發(fā)Dispatch Queue
并發(fā)隊列的好處人人皆知,可以方便的同時處理多個任務,在GCD中并發(fā)Dispatch Queue同樣遵循先進先出的原則,但這只是在運行時適用,如果有個任務在并發(fā)隊列中還沒輪到它執(zhí)行,那么此時完全可以移除它,而不必等它前面的任務執(zhí)行完成之后。至于并發(fā)隊列中沒次有多少個任務在執(zhí)行,這個恐怖在每一秒都在變化,因為影響它的因素有很多,所以之前說過,盡量不要移除移除已經添加進隊列的任務。
OS X和iOS系統(tǒng)為我們提供了四種全局并發(fā)Dispatch Queue,所謂全局隊列,就是我們不需要理會它們的保留和釋放問題,而且不需要專門創(chuàng)建它。與其說是四種不如說是一種全局并發(fā)隊列的四種不同優(yōu)先級,因為它們之間唯一的不同之處就是隊列優(yōu)先級不同。與Operation Queue不同,在GCD中,Dispatch Queue只有四種優(yōu)先級:
DISPATCH_QUEUE_PRIORITY_HIGH:高優(yōu)先級。DISPATCH_QUEUE_PRIORITY_DEFAULT:默認優(yōu)先級,低于高優(yōu)先級。DISPATCH_QUEUE_PRIORITY_LOW:低優(yōu)先級,低于高優(yōu)先級和默認優(yōu)先級。DISPATCH_QUEUE_PRIORITY_BACKGROUND:后臺優(yōu)先級,低于高優(yōu)先級和后臺線程執(zhí)行的任務。
我們可以通過dispatch_get_global_queue函數再根據不同的優(yōu)先級獲取不同的全局并發(fā)隊列,類型為dispatch_queue_t:
lethighPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) letdefaultPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
我們在使用全局并發(fā)隊列的時候不需要保留隊列的引用,隨時要用隨時用該函數獲取即可。當然我們也可以通過dispatch_queue_create函數自己創(chuàng)建隊列:
letconcurrentQueue = dispatch_queue_create(“com.example.MyConcurrentQueue”, DISPATCH_QUEUE_CONCURRENT)
從上面代碼可以看到,dispatch_queue_create函數有兩個參數,第一個為隊列的名稱,第二個為隊列類型,串行隊列為DISPATCH_QUEUE_SERIAL,并發(fā)隊列為DISPATCH_QUEUE_CONCURRENT。
串行Dispatch Queue
串行隊列可以讓我們將任務按照一定順序執(zhí)行,能更優(yōu)的處理多個任務之間的資源競爭問題,比線程鎖機制有更小的資源開銷和更好的性能,并且不會產生死鎖的問題。
系統(tǒng)也為我們提供了一個串行隊列,我們可以通過dispatch_get_main_queue函數獲?。?br /> letmainQueue = dispatch_get_main_queue()
該隊列與當前應用的主線程相關聯。當然我們也可以自己創(chuàng)建串行隊列:
letserialQueueA = dispatch_queue_create(“com.example.MySerialQueueA”, DISPATCH_QUEUE_SERIAL) // 或者letserialQueueB = dispatch_queue_create(“com.example.MySerialQueueB”, nil)
dispatch_queue_create函數的第二個參數如果為nil則默認創(chuàng)建串行隊列。當我們創(chuàng)建好串行隊列后,系統(tǒng)會自動將創(chuàng)建好的隊列與當前應用的主線程進行關聯。
獲取當前隊列
如果需要驗證或者測試當前隊列,我們可以通過dispatch_get_current_queue函數獲取當前隊列。如果在閉包中調用,返回的是該閉包所在的隊列,如果在閉包外調用,返回的則是默認的并發(fā)隊列。不過該函數在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()
從上面的代碼示例中可以看到,在執(zhí)行代碼點,我們用dispatch_set_context函數向serialQueue隊列的上下文環(huán)境中設置了一個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時代,資源釋放的工作已經基本不需要我們手動去做了,但有些時候因為系統(tǒng)釋放資源并不是很及時,也會造成內存移除等問題,所以在一些情況下我們還是需要進行手動釋放資源的工作,必入添加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函數作為清理函數,在該函數中獲得上下文數據,然后進行后續(xù)的清理工作。
?
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%
下載地址
iOS并發(fā)編程中Dispatch Queues的創(chuàng)建與管理的實踐總結下載
相關電子資料下載
- iOS17.1可能明天發(fā)布,iOS17.1主要修復哪些問題? 381
- 華為全新鴻蒙蓄勢待發(fā) 僅支持鴻蒙內核和鴻蒙系統(tǒng)應用 719
- 蘋果手機系統(tǒng)iOS 17遭用戶質疑 731
- iPhone12輻射超標?蘋果推送iOS 17.1解決此事 750
- 傳華為囤積零部件 目標明年智能手機出貨7000萬部;消息稱 MiOS 僅限國內,小米 28208
- 蘋果推送iOS17.0.3,解決iPhone15Pro系列存在機身過熱 216
- Testin云測兼容和真機服務平臺中上線iPhone 15系列手機 208
- 利爾達推出搭載HooRiiOS的Matter模組 145
- 運放參數解析:輸入偏置電流(Ibias)和失調電流(Ios) 128
- 昆侖太科發(fā)布支持國產飛騰騰銳D2000芯片的開源BIOS固件版本 448