iOS多线程Swift GCD 二:Dispatch Work

任务就是需要代码做的事,GCD负责提供完成任务的技巧;
GCD添加任务主要有两种形式,一种是通过闭包,一种是创建DispatchWorkItem;
还有比较特别的DispatchSource这里先不提

一: DispatchWorkItem

DispatchWorkItem是把任务本体,加上优先级和执行策略封装在一起.
并且优先级和执行策略可以有默认值
1.构造

        DispatchQueue.main.async(execute: DispatchWorkItem.init(block: {
            print("aaa")
        }))

      DispatchWorkItem.init(qos: .userInteractive, flags: .assignCurrentContext) {  
        }

优先级qos上一篇已经说了,这里说一下flags;
flags可以多选,默认是[];
a. assignCurrentContext 这个选项会使任务使用所在队列或者线程(或者说当前执行上下文)的属性设置,比如优先级
b. detached 这个选项是系统不会把当前线程或者队列的属性设置应用在这个任务上
c. enforceQoS 这个选项是将本任务的优先级相对当前上下文提升或者保持,总之不会降低
d. inheritQoS和上面相反,这个选项会会设置当前任务的优先级低于上下文,也就是可能会降低
e. noQoS 不指定优先级
f. barrier 和OC的barrier类似,都是阻塞队列,当作用与并发队列时,后面添加的任务会等待这个任务执行完毕后才开始.

        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如何使用;
创建了一个并发队列queue,当10到19时,创建barrier的DispatchWorkItem,添加到队列,而其他时候添加默认flags的任务;


image.png

可以看到barrier的任务会阻塞队列,连在一起并且按顺序输出,并且都在同一个线程中,而其他的任务会分布在很多个线程中.
这个例子就是常用的解决异步读写的方法,10到19相当于写入,其他的是读取;读与读之间可以异步进行,但是写和读,写和写之间必须是同步的,否则就是线程不安全的.
如果把这个例子中的queue换成DispatchQueue.global(),不管使用什么优先级,都不能成功阻塞队列,我猜想是系统提供的队列不能随便阻塞,毕竟里面可能还有系统的任务在,GCD会自己管理.

2.函数

  • perform()
        DispatchQueue.global().async {
            print(Thread.current)
            let work = DispatchWorkItem.init {
                print("aaa -- \(Thread.current)")
            }
            work.perform()
        }

perform()可以使任务直接在当前的线程中执行


image.png
  • DispatchTime
    DispatchTime是一个系统时间,通过各种函数来指定一个时长的值,但是这个时长不是数值类型,是一个结构体

DispatchTime.now()
现在,也就是DispatchTime(系统时间)延后0秒这么一个时长,但是系统肯定做不到真正的0,也就是不能真正的瞬间执行后面的事务,只能尽量接近0

DispatchTime.distantFuture
这个函数是设置无限大的时长

运算符函数
DispatchTime不是数值类型,不能直接比较和加减,因此apple专门提供了一套函数,使DispatchTime可以和double加减比较或者和其他DispatchTime加减比较.


image.png
  • DispatchWallTime
    DispatchWallTime是真实的系统时间,除此之外,和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才执行;


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执行完,而且working执行完之后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放在主队列中异步执行(同步会死锁),这时working和after都不会执行,只有before输出了;
只要把DispatchWorkItem添加到主队列,就不能使用wait,这会引起无限的等待.因为主队列的异步任务不会开启新的线程,而是会等待主线程当前的任务(UI,以及上面后几行代码)执行完;
所以这里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)当等待时间到达时,返回一个枚举DispatchTimeoutResult;
如果是timedOut则任务还没完成,如果是success则任务已经完成

  • Notify
func notify(queue: DispatchQueue, execute: DispatchWorkItem)

func notify(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], queue: DispatchQueue, execute: @escaping () -> Void)

和wait一样,也是为了线程间通信,可以在任务完成后,切换到另一个队列,执行下一个任务

        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为在执行完之把任务bbb添加到队列q2,然后异步执行work;
当work执行完后,bbb会执行,那么bbb是同步还是异步的呢,查看打印结果


image.png

发现abc分别在三个线程,所以bbb是异步的;
如果 q.async(execute: work)改成同步呢;
再打印一下看看


image.png

由于work是同步的,所以aaa走在了ccc前面,但是bbb仍然是异步的,因此notify方法添加的任务将会异步执行.
  • 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()压根不能中断正在执行的任务,因为本质上不是取消DispatchWorkItem的任务,只是标记为取消,当后续再次尝试执行时,如果标记了取消(isCancelled属性),则不会再尝试执行,对于已经开始的任务,并不会中断执行,说白了省得自定义一个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构造,使用上面两个方法添加任务,指定优先级等属性;
可以看到没有同步向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方法添加的任务,会在组内所有任务完成后执行.

        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会阻塞线程,会等待前面的任务完成,然后再执行后面的,因为添加任务的代码写在当前线程,所以它连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参数,这个参数的作用是设置一个最小的等待时间,如果等待时间过了,前面的任务还有没完成的,那也不等了,直接返回;
所以上面这个例子打印顺序是bca;
如果把等待时间改成6秒,就能等到a执行,会输出acb;

三.DispatchAfter

func asyncAfter(deadline: DispatchTime, execute: DispatchWorkItem)

func asyncAfter(deadline: DispatchTime, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], execute work: @escaping () -> Void)

DispatchAfter是队列的方法,有两个,只能添加异步任务,可以指定优先级;
对于deadline,是需要等待的时间,文档说明了两点,一是不能设置为现在(.now),这样做比直接执行效率要低;二是不能设置为永久(.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中执行.

四.其他补充

1.重复执行
class func concurrentPerform(iterations: Int, execute work: (Int) -> Void)

        DispatchQueue.concurrentPerform(iterations: 10) { (i) in
            print("i -- \(Thread.current)")
        }
image.png

这个方法可以高效的创建并发for循环,这是个静态方法所以不能指定队列,一定会创建很多个线程,所以尤其需要注意线程安全.

2.关于Target

convenience init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)

在自定义队列的时候,有一个属性是target,那么它是什么作用呢,文档里写了一大堆阅读理解,总结一下就是:
1.可以把队列Q的任务分配到target队列中,使用target的优先级策略,但是仍然保持Q的语义,比如串行或者并发
2.可以把多个队列的目标队列设置为同一个,这些任务会按照添加进target的顺序执行
3.不能相设置两个队列互为target
一般会用作给队列分组,将不同的任务放到多个队列管理,然后指向固定的队列或者系统队列再执行任务.

3.唯一执行
swift移除了Dispatch_once,但是在swift中有很多方法来实现唯一单次执行,比如全局的变量,类与结构体的静态属性等.

你可能感兴趣的:(iOS多线程Swift GCD 二:Dispatch Work)