首页 Operation VS DispatchWorkItem
文章
取消

Operation VS DispatchWorkItem

image

在多线程编程中我们总是拿 Operation 和 GCD 做比较 ,OperationQueue 的任务单元是 Operation ,但事实上 GCD 也有类似的任务单元 DispatchWorkItem,OperationQueue 在类似于取消、添加依赖等功能都由 Operation 提供,这其中显然忽略了和它很像的 DispatchWorkItem 存在,本文的主要内容就是对比这两者的差异。

start() vs perform()

基本的执行功能,两者都是可以独立执行的,并不一定需要依赖 OperationQueue 或是 DispatchQueue

1
2
3
4
let operation = BlockOperation {
    print(#function)
}
operation.start()
1
2
3
4
let workItem = DispatchWorkItem {
    print(#function)
}
workItem.perform()

cancel() vs cancel()

同样的都有取消功能,当然也有相应的isCancelled属性对应判断任务是否已经被取消

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lazy var workItem: DispatchWorkItem = {
     return DispatchWorkItem {
      sleep(2)
      guard self.workItem.isCancelled == false else {
          return
      }
      print(#function)
      }
  }()

  override func viewDidLoad() {
      super.viewDidLoad()
      DispatchQueue.global().async(execute: workItem)
  }

试着在两秒前取消任务,就不会有打印结果

1
2
3
@IBAction func buttonAction(_ sender: UIButton) {
		workItem.cancel()
}

waitUntilFinished() vs wait()

首先看waitUntilFinished() 官方的解释

Blocks execution of the current thread until the operation object finishes its task.(阻塞当前线程直到任务完成)

注意这里的当前线程,并不是任务执行的线程(主线程除外),举个例子:

1
2
3
4
5
6
7
8
9
10
override func viewDidLoad() {
  super.viewDidLoad()
	let operation = BlockOperation {
      Thread.sleep(forTimeInterval: 2)
      print(Thread.current)
  }
  OperationQueue().addOperation(operation)
  operation.waitUntilFinished()
  print(#function)
}

operation 的任务会在子线程等待两秒后完成,虽然任务丢到子线程执行,但主线程还是会被阻塞直到任务完成。

1
2
3
4
5
------主线程---------—————————————被阻塞————————————--------------->


                    |--------子线程执行任务-------->|

是不是很像同步操作,只不过任务的执行的被移到了其他线程。DispatchWorkItem 极其类似,这里就不做过多介绍。

1
2
3
4
5
6
let workItem = DispatchWorkItem {
    print(#function)
}

DispatchQueue.global().async(execute: workItem)
workItem.wait()

但 DispatchWorkItem 除了wait()还提供两个方法,用于判断任务是否超时。

1
2
3
public func wait(timeout: DispatchTime) -> DispatchTimeoutResult

public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult

同样看个例子,这里很简单,任务的执行的时间至少是两秒,设置的判断条件是一秒,所以会超时。

1
2
3
4
5
6
7
8
9
10
11
12
13
let workItem = DispatchWorkItem {
    Thread.sleep(forTimeInterval: 2)
    print(#function)
}

DispatchQueue.global().async(execute: workItem)
let result = workItem.wait(timeout: .now() + 1)
switch result {
case .success:
    print("success")
case .timedOut:
    print("timedOut")
}

另外关于两个 timeout 参数: DispatchTime 可以理解为相对时间或是应用内时间,DispatchWallTime 则是绝对时间或是世界时间。测试方法很简单,执行下面两个延时执行的例子,之后调整系统时间到五分钟后第二个回调会立即被执行。

1
2
3
4
5
6
DispatchQueue.main.asyncAfter(deadline: .now() + 300) {
    print(#function)
}
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 300) {
    print(#function)
}

addDependency(:) vs notify(::)

很多开发者会认为 OperationQueue 比起 GCD 在添加任务的依赖操作方面会更优雅甚至觉得 GCD 无法添加任务依赖,事实并非如此,通过 notify(queue: DispatchQueue, execute: DispatchWorkItem)依然可以很优雅的添加依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let workItem1 = DispatchWorkItem {
    Thread.sleep(forTimeInterval: 1)
    print(1)
}

let workItem2 = DispatchWorkItem {
    print(2)
}

let workItem3 = DispatchWorkItem {
    print(3)
}


workItem1.notify(queue: .global(), execute: workItem3)
workItem2.notify(queue: .global(), execute: workItem1)
DispatchQueue.global().async(execute: workItem2)

但试想一下,如果有个需求是想让 workItem1 最后执行,但不管其他任务的调用顺序,Operation 的实现方式就很简单和优雅,DispatchWorkItem 就无法做到这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let operation1 = BlockOperation {
    Thread.sleep(forTimeInterval: 1)
    print(1)
}

let operation2 = BlockOperation {
    print(2)
}

let operation3 = BlockOperation {
    print(3)
}

operation1.addDependency(operation2)
operation1.addDependency(operation3)
OperationQueue().addOperations([operation1, operation2, operation3], waitUntilFinished: false)

当然 DispatchGroup 也可以轻松满足这种需求,不过这个并不在本文的讨论范围内。

completionBlock: (() -> Void)? vs notify(::: () -> Void)

两个都可以设置一个完成回调

1
2
3
workItem1.notify(queue: .global()) {
    print("\(Thread.current)" + "1")
}
1
2
3
operation1.completionBlock = {
    print(#function)
}

qualityOfService vs qos

Operation 在初始化后可以设置 qualityOfService,这个属性的作用在这里就不做过多的介绍

1
operation.qualityOfService = .userInitiated

同样的 DispatchWorkItem 在初始化的时候 qos 参数就同等于 qualityOfService

1
2
3
DispatchWorkItem(qos: .userInitiated, flags: .barrier) {

}

至此 DispatchWorkItem 的所有方法都在本文中出现过,Operation 还有以下这些属性或方法没有在 DispatchWorkItem 找到对应的实现

1
2
3
4
5
6
7
8
9
10
open func main()
open var isExecuting: Bool { get }
open var isFinished: Bool { get }
open var isConcurrent: Bool { get }
open var isAsynchronous: Bool { get }
open var isReady: Bool { get }
open func removeDependency(_ op: Operation)
open var dependencies: [Operation] { get }
open var queuePriority: Operation.QueuePriority
open var name: String?

可以看到 Operation 额外提供了很多状态属性、移除依赖方法以及任务的优先级。所以单纯对比 DispatchWorkItem 和 Operation 来说,所谓的 GCD 的任务无法取消是错误的,OperationQueue 在添加任务依赖方面确实有优势。

本文由作者按照 CC BY 4.0 进行授权

你好2018

Range-面向协议编程