天天看点

翻译:Swift 5编写并发编程,并发解决方案和异步Operation说明异步与同步操作手动开始操作创建异步操作管理异步操作的状态添加取消支持利用异步任务结论参考

说明

异步操作允许执行长时间运行的任务,而不必阻塞调用线程,直到执行完成为止。这是建立关注点分离的好方法,特别是与在操作之间创建依赖项结合使用时。

如果您不熟悉操作,建议您先阅读博客文章 Swift中的Operations和OperationQueues入门。这篇文章可以帮助您入门并介绍基本知识。让我们开始研究异步操作,首先查看它们之间的区别及其同步的对立面。

异步与同步操作

看起来差别不大;实际上,它只是一个A,但实际差异要大得多。同步操作更容易设置和使用,但是只要异步操作不阻塞调用线程就无法运行。

异步操作可以发挥最大的作用,从而充分利用Swift中的操作。由于可以运行长时间运行的异步任务,因此可以将它们用于任何任务。创建关注点分离或将操作用作应用程序基础的核心逻辑。

综上所述,异步操作使您能够:

  • 运行长时间运行的任务
  • 从操作中调度到另一个队列
  • 手动开始操作,没有风险

我将在下一段中对最后一点做更多解释。

手动开始操作

同步和异步操作都可以手动启动。手动启动基本上可以归结为start()手动调用方法,而不是使用anOperationQueue来管理执行。

同步操作始终阻塞调用线程,直到操作完成。因此,它们不太适合手动启动操作。使用异步任务时,阻塞调用线程的风险不太重要,因为它有可能分派到另一个线程。

不建议手动启动

即使现在尝试手动启动异步任务可能很诱人,但也不建议这样做。通过使用an,

OperationQueue

您无需考虑执行多个操作时的执行顺序,并且可以从诸如优先任务等更多功能中受益。因此,建议始终通过将操作添加到中开始操作

OperationQueue

创建异步操作

创建异步操作都始于创建自定义子类并覆盖

isAsynchronous

属性。

class AsyncOperation: Operation {
    override var isAsynchronous: Bool {
        return true
    }

    override func main() {
        /// Use a dispatch after to mimic the scenario of a long-running task.
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), execute: {
            print("Executing")
        })
    }
}
           

这还不足以使任务异步,因为在执行print语句后,任务仍然直接进入完成状态。通过执行以下代码段可以证明这一点:

let operation = AsyncOperation()
let queue = OperationQueue()
queue.addOperations([operation], waitUntilFinished: true)
print("Operations finished")

// Prints:
// Operations finished
// Executing
           

换句话说,在异步任务仍在执行时,该任务已经标记为完成,这可能导致意外行为。我们需要自己开始管理状态,以使操作异步进行。

管理异步操作的状态

为了正确管理状态,我们需要使用多线程和KVO支持覆盖isFinishedandisExecuting属性。该isExecuting属性如下所示:

private var _isExecuting: Bool = false
override private(set) var isExecuting: Bool {
    get {
        return lockQueue.sync { () -> Bool in
            return _isExecuting
        }
    }
    set {
        willChangeValue(forKey: "isExecuting")
        lockQueue.sync(flags: [.barrier]) {
            _isExecuting = newValue
        }
        didChangeValue(forKey: "isExecuting")
    }
}
           

我们在仅同步访问的私有属性中跟踪执行状态。正如您在博客文章Concurrency in Swift中所了解的那样,您知道我们需要使用锁队列来进行线程安全的写和读访问。我们使用

willChangeValue(forKey:)

didChangeValue(forKey:)

来添加

KVO

支持,以确保

OperationQueue

正确更新获取的内容。

我们还需要覆盖

start()

更新状态的方法。重要的是要注意,

super.start()

因为我们现在正在自己处理状态,所以您永远不要调用此方法。

最后,我们添加了一个

finish()

方法,该方法允许我们在异步任务完成后将状态设置为完成。

将所有这些加在一起,我们得到一个看起来像这样的子类:

class AsyncOperation: Operation {
    private let lockQueue = DispatchQueue(label: "com.swiftlee.asyncoperation", attributes: .concurrent)

    override var isAsynchronous: Bool {
        return true
    }

    private var _isExecuting: Bool = false
    override private(set) var isExecuting: Bool {
        get {
            return lockQueue.sync { () -> Bool in
                return _isExecuting
            }
        }
        set {
            willChangeValue(forKey: "isExecuting")
            lockQueue.sync(flags: [.barrier]) {
                _isExecuting = newValue
            }
            didChangeValue(forKey: "isExecuting")
        }
    }

    private var _isFinished: Bool = false
    override private(set) var isFinished: Bool {
        get {
            return lockQueue.sync { () -> Bool in
                return _isFinished
            }
        }
        set {
            willChangeValue(forKey: "isFinished")
            lockQueue.sync(flags: [.barrier]) {
                _isFinished = newValue
            }
            didChangeValue(forKey: "isFinished")
        }
    }

    override func start() {
        print("Starting")
        isFinished = false
        isExecuting = true
        main()
    }

    override func main() {
        /// Use a dispatch after to mimic the scenario of a long-running task.
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), execute: {
            print("Executing")
            self.finish()
        })
    }

    func finish() {
        isExecuting = false
        isFinished = true
    }
}
           

为了确保我们的任务确实有效,我们将执行与之前相同的代码:

let operation = AsyncOperation()
let queue = OperationQueue()
queue.addOperations([operation], waitUntilFinished: true)
print("Operations finished")

// Prints:
// Starting
// Executing
// Operations finished
           

这很棒,正是我们想要的!唯一缺少的是取消。

添加取消支持

由于操作可以随时取消,因此在开始执行时需要考虑到这一点。可能是在任务开始之前操作已被取消。

我们可以通过在

start()

方法内部简单地添加一个防护来做到这一点:

override func start() {
    print("Starting")
    guard !isCancelled else { return }

    isFinished = false
    isExecuting = true
    main()
}

           

尽管

isFinishedandisExecuting

属性此时包含正确的值,但是我们仍然需要根据文档进行更新:

具体来说,您必须更改

finished to YES

返回的值和

executing to

返回的值

NO

。即使开始执行操作之前取消了操作,也必须进行这些更改。

因此,我们

finish()

start()

防护内部的方法中调用该方法,使最终方法如下所示:

override func start() {
    print("Starting")
    guard !isCancelled else {
        finish()
        return
    }

    isFinished = false
    isExecuting = true
    main()
}

           

利用异步任务

在为长时间运行的任务创建子类之后,是时候从中受益了。最终的异步操作类如下所示:

class AsyncOperation: Operation {
    private let lockQueue = DispatchQueue(label: "com.swiftlee.asyncoperation", attributes: .concurrent)

    override var isAsynchronous: Bool {
        return true
    }

    private var _isExecuting: Bool = false
    override private(set) var isExecuting: Bool {
        get {
            return lockQueue.sync { () -> Bool in
                return _isExecuting
            }
        }
        set {
            willChangeValue(forKey: "isExecuting")
            lockQueue.sync(flags: [.barrier]) {
                _isExecuting = newValue
            }
            didChangeValue(forKey: "isExecuting")
        }
    }

    private var _isFinished: Bool = false
    override private(set) var isFinished: Bool {
        get {
            return lockQueue.sync { () -> Bool in
                return _isFinished
            }
        }
        set {
            willChangeValue(forKey: "isFinished")
            lockQueue.sync(flags: [.barrier]) {
                _isFinished = newValue
            }
            didChangeValue(forKey: "isFinished")
        }
    }

    override func start() {
        print("Starting")
        guard !isCancelled else {
            finish()
            return
        }

        isFinished = false
        isExecuting = true
        main()
    }

    override func main() {
        fatalError("Subclasses must implement `main` without overriding super.")
    }

    func finish() {
        isExecuting = false
        isFinished = true
    }
}
           

main()

方法由子类执行时,我们将触发致命错误。

例如,您要上传带有的文件

FileUploadOperation

final class FileUploadOperation: AsyncOperation {

    private let fileURL: URL
    private let targetUploadURL: URL
    private var uploadTask: URLSessionTask?

    init(fileURL: URL, targetUploadURL: URL) {
        self.fileURL = fileURL
        self.targetUploadURL = targetUploadURL
    }

    override func main() {
        uploadTask = URLSession.shared.uploadTask(with: URLRequest(url: targetUploadURL), fromFile: fileURL) { (data, response, error) in
            // Handle the response
            // ...
            // Call finish
            self.finish()
        }
    }

    override func cancel() {
        uploadTask?.cancel()
        super.cancel()
    }
}
           

请注意,我们正在保存数据任务,因此可以根据需要取消它。

这只是一个非常基本的例子。在 Collect by WeTransfer应用程序中,我们经常使用以下操作:

  • Content creation内容创作
  • Content receiving内容接收
  • Content uploading内容上传
  • Content enriching内容丰富

还有更多。很棒的事情是,您可以将这些操作链接在一起,如上一篇有关操作入门的文章所述。

结论

而已!我们创建了异步操作,您可以在项目中直接使用它。希望它可以使您以更好的性能将关注点与代码更好地分离。

这篇文章是系列文章的一部分:

  • Swift中的Operations和OperationQueues入门
  • 在Swift中编写并发解决方案的异步操作(本文)
  • 通过使用泛型进行高级异步操作

也可以以Swift Playground的形式找到:https://github.com/AvdLee/AsyncOperations

如果您想进一步提高Swift知识,请查看 Swift类别页面。随意 与我联系 或鸣叫我在 Twitter的 ,如果您有任何额外的提示或反馈。

谢谢!

参考

https://www.avanderlee.com/swift/asynchronous-operations/