【译】Swift并发编程四

原文地址:https://ali-akhtar.medium.com/concurrency-in-swift-custom-operations-part-4-154b60bff84c

并发于并行的区别

这里是Swift并发编程系列第四篇(共四篇 、)。主要介绍如何实现自定义的异步Operation

如下图,我们创建了两个Operation并将它们都添加到Operation Queue中,此外,为了让任务2在任务1完成之后执行,我们让任务2依赖任务1。不出意外的话,它们都会自动在其他线程执行。

继续看下图,尽管我们添加了依赖规则(任务2依赖任务1),任务2依然没有等到任务1完成后再开始执行。原因是,任务1在向其他线程派发任务后就立即返回了,此时任务2就认为它执行完毕并启动了。在真实的应用中,我们经常会遇到类似问题。那么,如何解决?

通过手动控制任务执行的状态可以解决上面这个问题。NSOperation有默认的逻辑去追踪任务执行的状态。但我们将任务派发到其他线程后,这些逻辑就不正确了,我们必须手动控制任务具体何时结束。

Operation的状态

相比Dispatch groupOperations有更复杂的生命周期。这样给与我们更加灵活的控制权。Operation对象在其内部管理其生命周期,以便和外部交流,比如什么时候执行任务,什么时候任务完成了。所以,自定义以的子类需要控制其状态确保其任务能正确执行。我们可以通过以下keypath来操作状态:

isReady:告知客户端该任务已经准备好执行。大多数情况下,你不必手动操作该状态,除非你的任务依赖其他外部条件。此时,在外部条件由不满足转到满足后,你可以手动更改Operation的状态至此。

isExecuting:告知客户端该任务正在执行中。

isFinished:告知客户端任务已经正常结束或被取消并已退出。若存在对该任务的依赖关系,它将在isFinished变为true时才会清除。同样,串行队列只有在之前任务完成后才会出队下一任务。因此正确处理该值非常重要。

isCancelled:告知客户端任务已经请求取消。当然,一个任务是否支持被取消没有做强制要求。

正式开始之前

当任务开始时,它的状态从isReadyisExecuting。如果任务是异步的,比如下载图像,它会将调用发送到网络并立即返回。看起来它已经完成了。它不再在当前线程上做任何工作,但异步任务正在后台线程上运行。您需要一种方法来手动将操作状态设置为isExecuting 直到它真正完成。

操作状态属性是只读的。你不能直接设置它们。您必须做一些事情来使操作状态属性返回正确的值。操作类依赖KVO(键值观察)发送状态通知。

创建异步操作

这里我们要创建一个可重用的异步Operation。主要包含一下几点:

  1. 首先,创建一个枚举来表示Operation的状态。因为Operation的状态是只读的,我们无法直接设置其值,所以需要创建一个自己的状态。
  2. 我们将默认的状态设置为ready。通常情况下,不建议覆写isReady,因为系统会自动根据其依赖关系来决定。但是若你的Operation需要其他外部条件支持,你可以提供自己的实现。
  3. 这里我们只需根据自己定义的状态属性来覆写isExecutingisFinished
  4. 覆写isAsynchronous属性,这是实现异步Operation的必要条件。
  5. Operation是根据使用KVO来跟踪其具体状态的,所以我们需要在状态改变时发出KVO通知。这里我们使用属性监听器来完成这个工作。具体的,在willSet()中我们通知当前状态和下一个状态即将改变,在didSet()中我们通知之前的状态和新状态已经改变。
  6. 好了,KVO已经搞定。
  7. 为了方便获取KVOkeypath我们在状态枚举中实现了计算属性keypath
  8. 覆写start()cancel()方法。在start()方法中,检查该任务是否被取消,若取消我们将状态改为finished;若没有取消,调用main()方法开始任务,并将状态设置为executing。而cancel()方法直接将状态设置为finished.
  9. 注意:在start()方法中不要调用super.start()。根据官方文档,在实现异步Operation时,必须覆写该方法,去对Operation进行初始化。但是一定不要调用super。

接下来我们实现具体的异步Operation只需覆写main()方法即可。

可以看到,operation2operation1完成后才继续执行,并且他们都在子线程中执行。Operation Queue会自动的重用线程池中的线程。如下图,我们可以看到两个任务在同一线程中执行。

下面我们移除依赖关系,这两个任务会并发的在不同线程执行。

线程安全

之前的例子并非是线程安全的。可能存在多个线程同时设置state的情况。下面我们在state的操作过程中加上原子性来保证线程安全。

接下来,我们自定义了Operation Queue的标识。其背后依赖的underlyingQueue也会加上这个标志。

underlyingQueueOperation Queue用来分发任务的Dispatch Queue。该属性的默认值为nil。你可以手动设置该值,来确保Operation Queue使用的分发队列。前提是这个Operation Queue中的任务数量为0,否则将会抛出invalidArgumentException异常。这个值也不能是主队列。最后underlyingQueueqos会覆盖Operation Queue

下面我们在Operation Queue任务不为0的情况下设置了underlyingQueue。这将带了奔溃:

总结

连续的4篇主要是熟悉如何在Swift中使用异步编程。从OCSwift转变的过程中,相关API的变化还是比较大的。这里主要了解常用的功能及需要注意的点。希望大家有所收获。