任務就是需要代碼做的事,GCD負責提供完成任務的技巧;
GCD添加任務主要有兩種形式,一種是通過閉包,一種是創(chuàng)建DispatchWorkItem;
還有比較特別的DispatchSource這里先不提
一: DispatchWorkItem
DispatchWorkItem是把任務本體,加上優(yōu)先級和執(zhí)行策略封裝在一起.
并且優(yōu)先級和執(zhí)行策略可以有默認值
1.構造
DispatchQueue.main.async(execute: DispatchWorkItem.init(block: {
print("aaa")
}))
DispatchWorkItem.init(qos: .userInteractive, flags: .assignCurrentContext) {
}
優(yōu)先級qos上一篇已經說了,這里說一下flags;
flags可以多選,默認是[];
a. assignCurrentContext 這個選項會使任務使用所在隊列或者線程(或者說當前執(zhí)行上下文)的屬性設置,比如優(yōu)先級
b. detached 這個選項是系統(tǒng)不會把當前線程或者隊列的屬性設置應用在這個任務上
c. enforceQoS 這個選項是將本任務的優(yōu)先級相對當前上下文提升或者保持,總之不會降低
d. inheritQoS和上面相反,這個選項會會設置當前任務的優(yōu)先級低于上下文,也就是可能會降低
e. noQoS 不指定優(yōu)先級
f. barrier 和OC的barrier類似,都是阻塞隊列,當作用與并發(fā)隊列時,后面添加的任務會等待這個任務執(zhí)行完畢后才開始.
let queue = DispatchQueue.init(label: "queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
for index in 0 ..< 100 {
if index >= 10 && index < 20{
let work = DispatchWorkItem.init(qos: .default, flags: .barrier) {
print("barrier\(index)")
}
queue.async(execute: work)
}else{
queue.async {
print(index)
}
}
}
上面這段代碼展示了barrier如何使用;
創(chuàng)建了一個并發(fā)隊列queue,當10到19時,創(chuàng)建barrier的DispatchWorkItem,添加到隊列,而其他時候添加默認flags的任務;
可以看到barrier的任務會阻塞隊列,連在一起并且按順序輸出,并且都在同一個線程中,而其他的任務會分布在很多個線程中.
這個例子就是常用的解決異步讀寫的方法,10到19相當于寫入,其他的是讀取;讀與讀之間可以異步進行,但是寫和讀,寫和寫之間必須是同步的,否則就是線程不安全的.
如果把這個例子中的queue換成DispatchQueue.global(),不管使用什么優(yōu)先級,都不能成功阻塞隊列,我猜想是系統(tǒng)提供的隊列不能隨便阻塞,畢竟里面可能還有系統(tǒng)的任務在,GCD會自己管理.
2.函數(shù)
- perform()
DispatchQueue.global().async {
print(Thread.current)
let work = DispatchWorkItem.init {
print("aaa -- \(Thread.current)")
}
work.perform()
}
perform()可以使任務直接在當前的線程中執(zhí)行
- DispatchTime
DispatchTime是一個系統(tǒng)時間,通過各種函數(shù)來指定一個時長的值,但是這個時長不是數(shù)值類型,是一個結構體
DispatchTime.now()
現(xiàn)在,也就是DispatchTime(系統(tǒng)時間)延后0秒這么一個時長,但是系統(tǒng)肯定做不到真正的0,也就是不能真正的瞬間執(zhí)行后面的事務,只能盡量接近0
DispatchTime.distantFuture
這個函數(shù)是設置無限大的時長
運算符函數(shù)
DispatchTime不是數(shù)值類型,不能直接比較和加減,因此apple專門提供了一套函數(shù),使DispatchTime可以和double加減比較或者和其他DispatchTime加減比較.
DispatchWallTime
DispatchWallTime是真實的系統(tǒng)時間,除此之外,和DispatchTime完全一致wait()
let work = DispatchWorkItem.init {
sleep(2)
print("working -- \(Thread.current)")
}
DispatchQueue.global().async(execute: work)
print("before -- \(Thread.current)")
work.wait()
print("after -- \(Thread.current)")
等待work完成之后,after才執(zhí)行;
這其實是一個線程間通信的效果,before和after都在主線程,而working在其他線程,wait添加在主線程,主線程就會等待另一個線程完成
let work = DispatchWorkItem.init {
sleep(2)
print("working -- \(Thread.current)")
}
DispatchQueue.global().sync(execute: work)
print("before -- \(Thread.current)")
work.wait()
print("after -- \(Thread.current)")
把上面例子的 DispatchQueue.global().async改成 DispatchQueue.global().sync,由于沒有開啟新線程,before上來就得等working執(zhí)行完,而且working執(zhí)行完之后wait也不再生效了,直接return
let work = DispatchWorkItem.init {
sleep(2)
print("working -- \(Thread.current)")
}
DispatchQueue.main.async(execute: work)
print("before -- \(Thread.current)")
work.wait()
print("after -- \(Thread.current)")
再改造一下,把work放在主隊列中異步執(zhí)行(同步會死鎖),這時working和after都不會執(zhí)行,只有before輸出了;
只要把DispatchWorkItem添加到主隊列,就不能使用wait,這會引起無限的等待.因為主隊列的異步任務不會開啟新的線程,而是會等待主線程當前的任務(UI,以及上面后幾行代碼)執(zhí)行完;
所以這里work還沒開始,就先wait了.
wait()可以指定等待時間,直接調用和使用wait(timeout:.distantFuture)是一樣的效果
let queue = DispatchQueue.init(label: "aaa")
let work = DispatchWorkItem.init {
sleep(1)
}
queue.async(execute: work)
let time = work.wait(timeout: .now() + 3)
if time == DispatchTimeoutResult.timedOut{
print("未完成")
}else if time == DispatchTimeoutResult.success{
print("已完成")
}
work.wait(timeout: .now() + 3)當?shù)却龝r間到達時,返回一個枚舉DispatchTimeoutResult;
如果是timedOut則任務還沒完成,如果是success則任務已經完成
- Notify
func notify(queue: DispatchQueue, execute: DispatchWorkItem)
func notify(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], queue: DispatchQueue, execute: @escaping () -> Void)
和wait一樣,也是為了線程間通信,可以在任務完成后,切換到另一個隊列,執(zhí)行下一個任務
let q = DispatchQueue.global()
let work = DispatchWorkItem.init(block: {
print("aaa -- \(Thread.current)")
sleep(2)
})
let q2 = DispatchQueue.init(label: "q2")
work.notify(queue: q2) {
print("bbb -- \(Thread.current)")
}
q.async(execute: work)
print("ccc -- \(Thread.current)")
這段代碼向隊列q中添加一個任務work(aaa),work設置notify為在執(zhí)行完之把任務bbb添加到隊列q2,然后異步執(zhí)行work;
當work執(zhí)行完后,bbb會執(zhí)行,那么bbb是同步還是異步的呢,查看打印結果
發(fā)現(xiàn)abc分別在三個線程,所以bbb是異步的;
如果 q.async(execute: work)改成同步呢;
再打印一下看看
由于work是同步的,所以aaa走在了ccc前面,但是bbb仍然是異步的,因此notify方法添加的任務將會異步執(zhí)行.
- cancel()
let q = DispatchQueue.global()
item = DispatchWorkItem.init(block: { [weak self] in
for i in 0 ... 100000{
if self?.item?.isCancelled ?? false{
break
}
print(i)
}
})
if item != nil{
q.async(execute: item!)
}
q.asyncAfter(deadline: .now() + 0.1) {
self.item?.cancel()
}
這段代碼只能輸出幾千次,乍一看是中斷了任務,實際上如果把break那三行注釋掉,還是會輸出100000次.
從這個例子能看出來,cancel()壓根不能中斷正在執(zhí)行的任務,因為本質上不是取消DispatchWorkItem的任務,只是標記為取消,當后續(xù)再次嘗試執(zhí)行時,如果標記了取消(isCancelled屬性),則不會再嘗試執(zhí)行,對于已經開始的任務,并不會中斷執(zhí)行,說白了省得自定義一個flag而已.
二: DispatchGroup
DispatchGroup和OC的Group基本一樣,簡單舉個例子
func async(group: DispatchGroup, execute workItem: DispatchWorkItem)
func async(group: DispatchGroup? = nil, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping @convention(block) () -> Void)
直接init構造,使用上面兩個方法添加任務,指定優(yōu)先級等屬性;
可以看到沒有同步向group添加任務的方法,只能異步.
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group){
print("aaa")
}
DispatchQueue.global().async(group: group){
print("bbb")
}
DispatchQueue.global().async(group: group){
print("ccc")
}
group.notify(queue: DispatchQueue.main) {
print("finish")
}
DispatchGroup的notify方法添加的任務,會在組內所有任務完成后執(zhí)行.
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group){
DispatchQueue.global().async {
sleep(2)
print("aaa")
}
}
DispatchQueue.global().async(group: group){
print("bbb")
}
DispatchQueue.global().async(group: group){
print("ccc")
}
group.notify(queue: DispatchQueue.main) {
print("finish")
}
上面這樣的例子,group添加的第一個任務會立即返回,導致finish比aaa更早輸出,這里可以使用enter()好leave()來控制,和OC的使用起來完全一樣.
let group = DispatchGroup.init()
group.enter()
DispatchQueue.global().async(group: group){
DispatchQueue.global().async {
sleep(2)
print("aaa")
group.leave()
}
}
DispatchQueue.global().async(group: group){
print("bbb")
}
DispatchQueue.global().async(group: group){
print("ccc")
}
group.notify(queue: DispatchQueue.main) {
print("finish")
}
只需要給異步的任務加上enter和leave就可以了
- wait
DispatchGroup也有wait()方法,wait會阻塞線程,會等待前面的任務完成,然后再執(zhí)行后面的,因為添加任務的代碼寫在當前線程,所以它連group自己也能阻塞.
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group) {
sleep(2)
print("a")
}
// let _ = group.wait()
DispatchQueue.global().async(group: group) {
print("b")
}
print("c")
在上面這個例子里,注釋wait打印順序是cba,打開注釋,打印順序是acb
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group) {
sleep(2)
print("a")
}
DispatchQueue.global().async(group: group) {
print("b")
}
let _ = group.wait()
print("c")
調整一下位置,打印順序是bac
let group = DispatchGroup.init()
DispatchQueue.global().async(group: group) {
sleep(5)
print("a")
}
let _ = group.wait(timeout: .now() + 3)
DispatchQueue.global().async(group: group) {
print("b")
}
print("c")
wait可以設置一個DispatchTime參數(shù),這個參數(shù)的作用是設置一個最小的等待時間,如果等待時間過了,前面的任務還有沒完成的,那也不等了,直接返回;
所以上面這個例子打印順序是bca;
如果把等待時間改成6秒,就能等到a執(zhí)行,會輸出acb;
三.DispatchAfter
func asyncAfter(deadline: DispatchTime, execute: DispatchWorkItem)
func asyncAfter(deadline: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping () -> Void)
DispatchAfter是隊列的方法,有兩個,只能添加異步任務,可以指定優(yōu)先級;
對于deadline,是需要等待的時間,文檔說明了兩點,一是不能設置為現(xiàn)在(.now),這樣做比直接執(zhí)行效率要低;二是不能設置為永久(.distantFuture),這么做沒意義
let q = DispatchQueue.init(label: "queue")
q.async {
q.asyncAfter(deadline: .now() + 0.001) {
print("b -- \(Thread.current)")
}
for i in 0 ..< 100000{
print("a = \(i) -- \(Thread.current)")
}
}
上面這段代碼中a會打印很久,b要一直等到a輸出完,才能輸出,因此DispatchAfter并不會開啟新的線程,a和b都在線程4中執(zhí)行.
四.其他補充
1.重復執(zhí)行
class func concurrentPerform(iterations: Int, execute work: (Int) -> Void)
DispatchQueue.concurrentPerform(iterations: 10) { (i) in
print("i -- \(Thread.current)")
}
這個方法可以高效的創(chuàng)建并發(fā)for循環(huán),這是個靜態(tài)方法所以不能指定隊列,一定會創(chuàng)建很多個線程,所以尤其需要注意線程安全.
2.關于Target
convenience init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
在自定義隊列的時候,有一個屬性是target,那么它是什么作用呢,文檔里寫了一大堆閱讀理解,總結一下就是:
1.可以把隊列Q的任務分配到target隊列中,使用target的優(yōu)先級策略,但是仍然保持Q的語義,比如串行或者并發(fā)
2.可以把多個隊列的目標隊列設置為同一個,這些任務會按照添加進target的順序執(zhí)行
3.不能相設置兩個隊列互為target
一般會用作給隊列分組,將不同的任務放到多個隊列管理,然后指向固定的隊列或者系統(tǒng)隊列再執(zhí)行任務.
3.唯一執(zhí)行
swift移除了Dispatch_once,但是在swift中有很多方法來實現(xiàn)唯一單次執(zhí)行,比如全局的變量,類與結構體的靜態(tài)屬性等.