iOS Concurrency-队列、DispatchGroup
发布日期:2022-03-18 08:27:40 浏览次数:39 分类:技术文章

本文共 7494 字,大约阅读时间需要 24 分钟。

本文内容来自

iOS Concurrency-队列、DispatchGroup

Swift 3

在Swift3中,GCD有关的API发生了一些变化,可参考:

获取系统队列

1.获取系统主队列:

let mainQ = DispatchQueue.main

2.获取系统的全局队列

DispatchQueue.global(qos: .userInteractive)DispatchQueue.global(qos: .userInitiated)DispatchQueue.global() // .default qosDispatchQueue.global(qos: .utility)DispatchQueue.global(qos: .background)DispatchQueue.global(qos: .unspecified)

QoS的全称是quality of service。在Swift 3中,它是一个结构体,用来制定队列或者任务的重要性。

  • User Interactive 和用户交互相关,比如动画等等优先级最高。比如用户连续拖拽的计算
  • User Initiated 需要立刻的结果,比如push一个ViewController之前的数据计算
  • Utility 可以执行很长时间,再通知用户结果。比如下载一个文件,给用户下载进度。
  • Background 用户不可见,比如在后台存储大量数据

用户创建队列

1.创建串行队列

let mySerialQ = DispatchQueue(label: "com.raywenderlich.serial")

2.创建并行队列

let workerQ = DispatchQueue(label: “com.raywenderlich.worker",attributes: .concurrent)

常用的方式是:

DispatchQueue.global().async {    // do expensive synchronous task    DispatchQueue.main.async {        // update UI when task finishes    }}

串行队列通常用来setget属性的值,避免race conditions

private let internalQueue = DispatchQueue(label:"com.raywenderlich.person.internal")var name: String {    get {        return internalQueue.sync { internalName }    }    set (newName) {        internalQueue.sync { internalName = newName }    }}

如何理解串行队列的asyncsync

可参考,可发现,asyncsync的区别,在于return返回的时机,sync需等待block执行完毕再返回,而async则不需等待

DispatchGroup

如果想在dispatch_queue中所有的任务执行完成后再做某种操作可以使用DispatchGroup。原先的dispatch_group_t由现在的DispatchGroup对象代替。

let slowAddGroup = DispatchGroup()

group中每个任务都完成后,会调用notify方法

wait方法,会等待group完成,要注意的是,此方法是同步,会阻塞当前的线程

如下的例子,模拟一个耗时操作slowAdd,对数组中的元素相加:

public func slowAdd(_ input: (Int, Int)) -> Int {  sleep(1)  return input.0 + input.1}

对多个数据操作,在全部任务完成后得到通知,再做其它的操作,如下:

let workerQueue = DispatchQueue(label: "com.raywenderlich.worker", attributes: .concurrent)let numberArray = [(0,1), (2,3), (4,5), (6,7), (8,9)]print("=== Group of sync tasks ===\n")let slowAddGroup = DispatchGroup()for inValue in numberArray{    workerQueue.async(group: slowAddGroup) {        let result = slowAdd(inValue)        print("\(inValue) = \(result)")    }}//全部任务完成后的调用let defaultQueue = DispatchQueue.global()slowAddGroup.notify(queue: defaultQueue){    print("Done")}//等待group中任务完成print("wait before......")slowAddGroup.wait(timeout: DispatchTime.distantFuture)print("wait after......")

其输出结果如下:

=== Group of sync tasks ===wait before......(0, 1) = 1(2, 3) = 5(4, 5) = 9(8, 9) = 17(6, 7) = 13Donewait after......

还有一个问题是,假设你想把一个异步执行的方法包含在一个dipatch group中,而这异步执行的方法并没有提供一个dipatch group参数,所以该如何对现存的asynchronous API使用dispatch groups?在实际的项目中,经常会遇到这样的例子,如,有多个网络请求,需要在所有网络请求完成后,做某些事情,该怎么做呢?

可使用group.enter()group.leave(),这两者要成对的出现

通过例子来说明,还是上面slowAdd的例子,不过,把它封装在一个异步的方法中执行:

1.runQueue表示执行的队列

2.completionQueue表示完成task后,completion回调执行的队列

func asyncAdd(_ input: (Int, Int), runQueue: DispatchQueue, completionQueue: DispatchQueue, completion: @escaping (Int, Error?) -> ()) {    runQueue.async {        var error: Error?        error = .none        let result = slowAdd(input)        completionQueue.async { completion(result, error) }    }}

现在,有一组数据要执行asyncAdd,想要在所有的任务都执行完后,得到通知。

创建一个asyncAdd_Group方法,把asyncAdd添加到group中,如下:

func asyncAdd_Group(_ input: (Int, Int), runQueue: DispatchQueue, completionQueue: DispatchQueue, group: DispatchGroup, completion: @escaping (Int, Error?) -> ()) {    group.enter()    asyncAdd(input, runQueue: runQueue, completionQueue: completionQueue) { (result, error) in        completion(result, error)        group.leave()    }}

注意上面的group.enter()group.leave()

调用的过程如下:

//创建grouplet wrappedGroup = DispatchGroup()//循环调用asyncAdd_Groupfor pair in numberArray {    asyncAdd_Group(pair, runQueue: workerQueue, completionQueue: defaultQueue, group: wrappedGroup, completion: { (result, error) in        print("\(pair) = \(result)")    })}//通知wrappedGroup.notify(queue: defaultQueue) {     print("Async Done!")}

其输出结果如下,达到预期的效果:

(6, 7) = 13(0, 1) = 1(2, 3) = 5(4, 5) = 9(8, 9) = 17Async Done!

实际使用的例子

1.UIView的动画API是异步的,所以当有多个动画的时候,可以使用DispatchGroup,在动画都完成是得到通知

如下,扩展UIView,原理与上面讲的一样,使用DispatchGroup,添加group.enter()group.leave()

extension UIView {  static func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void, group: DispatchGroup, completion: ((Bool) -> Void)?) {    group.enter()    UIView.animate(withDuration: duration, animations: animations) { (success) in        completion?(success)        group.leave()    }  }}

使用方式如下:

首先创建group

let animationGroup = DispatchGroup()

然后,调用上面的扩展方法即可:

UIView.animate(withDuration: 1, animations: {     box.center = CGPoint(x: 150, y: 150)}, group: animationGroup) { _ in    UIView.animate(withDuration: 2, animations: {         box.transform = CGAffineTransform(rotationAngle: .pi/4)    }, group: animationGroup, completion: .none)}animationGroup.notify(queue: DispatchQueue.main) {  print("Animations Completed!")}

并发问题

在并发编程中可能出现的问题:

  • Race condition
  • Priority inversion
  • Deadlock

一些建议:

  • One QoS for tasks accessing shared resource
  • Serial queue to access shared resource
  • Avoid Operation dependency cycles
  • Be careful when calling sync()
  • Never call sync() on the current queue
  • Never ever call sync() from the main queue

实际问题

1.在异步调用的时候要考虑线程安全的问题

如下的Person类,其中有个changeName方法来修改firstNamelastName,注意方法中延迟:

open class Person {  private var firstName: String  private var lastName: String  public init(firstName: String, lastName: String) {    self.firstName = firstName    self.lastName = lastName  }  //修改name  open func changeName(firstName: String, lastName: String) {    randomDelay(maxDuration:  0.2)    self.firstName = firstName    randomDelay(maxDuration:  1)    self.lastName = lastName  }  open var name: String {    return "\(firstName) \(lastName)"  }}//延迟func randomDelay(maxDuration: Double) {  let randomWait = arc4random_uniform(UInt32(maxDuration * Double(USEC_PER_SEC)))  usleep(randomWait)}

如下,在一个并发的队列中异步执行changeName操作:

let workerQueue = DispatchQueue(label: "com.raywenderlich.worker", attributes: .concurrent)let nameChangeGroup = DispatchGroup()let nameList = [("Charlie", "Cheesecake"), ("Delia", "Dingle"), ("Eva", "Evershed"), ("Freddie", "Frost"), ("Gina", "Gregory")]for (idx, name) in nameList.enumerated() {  workerQueue.async(group: nameChangeGroup) {    usleep(UInt32(10_000 * idx))    nameChangingPerson.changeName(firstName: name.0, lastName: name.1)    print("Current Name: \(nameChangingPerson.name)")  }}nameChangeGroup.notify(queue: DispatchQueue.global()) {  print("Final name: \(nameChangingPerson.name)")  PlaygroundPage.current.finishExecution()}

其输出结果为:

Current Name: Gina DingleCurrent Name: Gina FrostCurrent Name: Gina CheesecakeCurrent Name: Gina GregoryCurrent Name: Gina EvershedFinal name: Gina Evershed

会发现结果完全不正确,这里的问题是一个线程在写的时候,另一个线程也在写,所以有race condition的问题,所以Person类并不是线程安全的

此时可以使用Dispatch Barrier创建线程安全的Person

class ThreadSafePerson: Person {
let isolationQueue = DispatchQueue(label: "com.raywenderlich.person.isolation", attributes: .concurrent) override func changeName(firstName: String, lastName: String) { isolationQueue.async(flags: .barrier) { super.changeName(firstName: firstName, lastName: lastName) } } override var name: String { return isolationQueue.sync { return super.name } }}

2.SYNC READING/WRITING VALUES

private let internalQueue = DispatchQueue(label:"com.raywenderlich.person.internal")var name: String {    get {        return internalQueue.sync { internalName }    }    set (newName) {        internalQueue.sync { internalName = newName }    }}

3.通过Thread Sanitizer查找出可能的线程问题

这里写图片描述

转载地址:https://windzen.blog.csdn.net/article/details/52774454 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Having fun with NSOperations in iOS
下一篇:Swift中的协议

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月08日 19时37分42秒