iOS多線程Swift GCD 二:Dispatch Work

任務就是需要代碼做的事,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的任務;


image.png

可以看到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í)行


image.png
  • 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加減比較.


image.png
  • 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í)行;


image.png

這其實是一個線程間通信的效果,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


image.png
      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)")
image.png

再改造一下,把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是同步還是異步的呢,查看打印結果


image.png

發(fā)現(xiàn)abc分別在三個線程,所以bbb是異步的;
如果 q.async(execute: work)改成同步呢;
再打印一下看看


image.png

由于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")
        }
image.png

上面這樣的例子,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就可以了


image.png
  • 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)")
            }
        }

image.png

上面這段代碼中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)")
        }
image.png

這個方法可以高效的創(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)屬性等.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末婚惫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌音婶,老刑警劉巖蚜迅,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件英岭,死亡現(xiàn)場離奇詭異哟绊,居然都是意外死亡铜跑,警方通過查閱死者的電腦和手機门怪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锅纺,“玉大人掷空,你說我怎么就攤上這事《陲保” “怎么了坦弟?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長官地。 經常有香客問我酿傍,道長,這世上最難降的妖魔是什么区丑? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任拧粪,我火速辦了婚禮修陡,結果婚禮上沧侥,老公的妹妹穿的比我還像新娘。我一直安慰自己魄鸦,他們只是感情好宴杀,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拾因,像睡著了一般旺罢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绢记,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天扁达,我揣著相機與錄音,去河邊找鬼蠢熄。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的凳怨。 我是一名探鬼主播华临,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饥追!你這毒婦竟也來了图仓?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤但绕,失蹤者是張志新(化名)和其女友劉穎救崔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡六孵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年碳竟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狸臣。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡莹桅,死狀恐怖,靈堂內的尸體忽然破棺而出烛亦,到底是詐尸還是另有隱情诈泼,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布煤禽,位于F島的核電站铐达,受9級特大地震影響,放射性物質發(fā)生泄漏檬果。R本人自食惡果不足惜瓮孙,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望选脊。 院中可真熱鬧杭抠,春花似錦、人聲如沸恳啥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钝的。三九已至翁垂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硝桩,已是汗流浹背沿猜。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碗脊,地道東北人啼肩。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像望薄,于是被迫代替她去往敵國和親疟游。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內容