翻译: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 Playground的形式找到:https://github.com/AvdLee/AsyncOperations

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

谢谢!

参考

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

# 关于 Swift Swift 是一种非常好的编写软件的方式,无论是手机,台式机,服务器,还是其他运行代码的设备。它是一种安全,快速和互动的编程语言,将现代编程语言的精华和苹果工程师文化的智慧,以及来自开源社区的多样化贡献结合了起来。编译器对性能进行了优化,编程语言对开发进行了优化,两者互不干扰,鱼与熊掌兼得。 Swift 对于初学者来说也很友好。它是第一个既满足工业标准又像脚本语言一样充满表现力和趣味的系统编程语言。它支持代码预览(playgrounds),这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。 Swift 通过采用现代编程模式来避免大量常见编程错误: * 变量始终在使用前初始化。 * 检查数组索引超出范围的错误。 * 检查整数是否溢出。 * 可选值确保明确处理 `nil` 值。 * 内存被自动管理。 * 错误处理允许从意外故障控制恢复。 Swift 代码被编译和优化,以充分利用现代硬件。语法和标准库是基于指导原则设计的,编写代码的明显方式也应该是最好的。安全性和速度的结合使得 Swift 成为从 “Hello,world!” 到整个操作系统的绝佳选择。 Swift 将强大的类型推理和模式匹配与现代轻巧的语法相结合,使复杂的想法能够以清晰简洁的方式表达。因此,代码不仅更容易编写,而且易于阅读和维护。 Swift 已经进行了多年,并且随着新特性和功能的不断发展。我们对 Swift 的目标是雄心勃勃的。我们迫不及待想看到你用它创建出的东西。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值