并发编程 - NSOperationNSOperationQueue(多线程)

embedded/2024/12/21 20:31:50/

引言

在上篇博客中我们首先介绍了GCD的多线程方案,NSOperationNSOperationQueue是Apple为我们提供的另一个并发编程框架的高级抽象,用于简化和管理复杂的多线程任务。事实上它基于GCD的高层封装,提供了更强大的功能和更灵活的控制。

尽管GCD非常强大,但在某些场景下,开发者需要对任务的管理有更多的控制,比如任务的依赖关系,取消任务,任务完成后的处理等,这时候NSOperationNSOperationQueue的优势就凸显出来了,他可以帮助开发者更方便地管理复杂的并发任务。

概念

NSOperation(操作)

NSOperation是一个抽象类,代表一个可以被加入到队列中执行的操作。每个NSOperation实例都代表一个独立的任务,这些任务可以被添加到NSOperationQueue中,并按照指定的顺序和依赖关系来执行。

NSOperation提供了对任务的基本控制功能,例如开始、暂停、取消,以及查询任务的状态。它还可以管理任务的执行顺序,通过设置任务之间的依赖关系,确保某个任务在另一个任务完成之后再执行。

此外,NSOperation可以通过子类化来创建自定义的操作,或者直接使用其子类BlockOperation来快速封装任务。

NSOperationQueue(操作队列)

NSOperationQueue是一个用于调度和管理NSOperation的队列,负责控制NSOperation的执行顺序、并发性以及任务间的依赖关系。

NSOperationQueue可以同时执行多个操作,并通过设置最大并发操作数来控制同时执行的任务数量。根据操作之间的依赖关系,NSOperationQueue能够自动调度任务的执行顺序。

此外,NSOperationQueue允许在运行时取消或者暂停队列中的操作,并控制它们的执行。

NSOperationQueue还提供了一个特殊的队列,成为主队列(mainQueue),它用于在主线程上执行操作,非常适合需要更新UI的任务。

使用

直接执行任务

对于NSOperation我们需要使用它的子类比如BlockOperation,使用它我们可以通过代码块(block)来创建任务,并且可以直接执行任务。

    func testOperation() {let operation = BlockOperation {for i in 0...5 {print("BlockOperation:\(i)")}}operation.start()}

通过队列管理操作

不过通常来讲,我们会把操作和操作队列组合在一起使用,通过操作队列来自动管理队列中操作的执行,而不是手动调用start()方法。

    // MARK: - 创建OperationQueueprivate func testOperationQueue() {let operationQueue = OperationQueue()let operation1 = BlockOperation {for i in 0...5 {print("BlockOperation:\(i)")}}let operation2 = BlockOperation {for i in 0...5 {print("BlockOperation:\(i)")}}operationQueue.addOperation(operation1)operationQueue.addOperation(operation2)}

设置优先级

NSOperation提供了一种简单的方式来设置操作的优先级(queuePriority)。通过设置操作的优先级,我们可以在队列中多个操作等待执行时,控制哪些操作应该优先执行。优先级从.veryLow到.veryHigh共有5个级别。

    // MARK: - 设置优先级private func testPriority() {let operationQueue = OperationQueue()let operation1 = BlockOperation {for i in 0...5 {print("BlockOperation1:\(i)")}}let operation2 = BlockOperation {for i in 0...5 {print("BlockOperation2:\(i)")}}let operation3 = BlockOperation {for i in 0...5 {print("BlockOperation3:\(i)")}}operation1.queuePriority = .normaloperation2.queuePriority = .highoperation3.queuePriority = .lowoperationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: false)}

需要注意的是:

优先级只在所有操作都处于准备状态时才有效,不能覆盖操作之间的依赖关系。

比如,当多个图片处理人物同时被添加到队列中,我们可以设置其中一个任务为高优先级,确保它比其他任务更早执行。

设置依赖关系

NSOperation提供了非常简单的方式来设置依赖关系,这在需要严格按照先后顺序执行的场景中非常有用。通过将一个操作设置为另一个操作的依赖项,我们可以确保操作按照预期的顺序执行,从而轻松管理复杂的任务流程。例如,在处理网络请求、文件操作或任务链条时,依赖关系能够帮助我们在不引入大量额外代码的情况下,轻松控制任务的执行顺序。

    // MARK: - 设置依赖private func testDependency() {let operationQueue = OperationQueue()let operation1 = BlockOperation {for i in 0...5 {print("BlockOperation1:\(i)")}}let operation2 = BlockOperation {for i in 0...5 {print("BlockOperation2:\(i)")}}let operation3 = BlockOperation {for i in 0...5 {print("BlockOperation3:\(i)")}}operation2.addDependency(operation1)operation3.addDependency(operation2)operationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: false)}

设置最大并发数

NSOperationQueue是一个并发队列,默认的最大并发数为OperationQueue.defaultMaxConcurrentOperationCount,但实际上这并不是一个固定的值,它是一个动态计算的值,取决于系统资源,硬件能力和其他因素。

但我们可以通过maxConcurrentOperationCount来修改队列的最大并发数。

    //MARK: - 设置最大并发数private func testMaxConcurrentOperationCount() {let operationQueue = OperationQueue()operationQueue.maxConcurrentOperationCount = 2let operation1 = BlockOperation {for i in 0...5 {print("BlockOperation1:\(i)")sleep(1)}}let operation2 = BlockOperation {for i in 0...5 {print("BlockOperation2:\(i)")sleep(1)}}let operation3 = BlockOperation {for i in 0...5 {print("BlockOperation3:\(i)")sleep(1)}}operationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: false)}

阻塞当前线程

队列还为我们提供了一个确保队列中所有的操作都已经执行完毕后再继续执行后面代码的方法,这在实际开发中有着广泛的应用场景,例如,在处理多个媒体资源的上传后统一发布,或者在多个数据资源加载完成后统一渲染时,waitUntilAllOperationsAreFinished方法可以确保所有必要的操作都已经完成。

    //MARK: - 阻塞线程private func testWaitUntilFinished() {let operationQueue = OperationQueue()let operation1 = BlockOperation {for i in 0...5 {print("BlockOperation1:\(i)")}}let operation2 = BlockOperation {for i in 0...5 {print("BlockOperation2:\(i)")}}let operation3 = BlockOperation {for i in 0...5 {print("BlockOperation3:\(i)")}}operationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: true)print("finished")}

但是我们在使用阻塞线程的方法时,需要注意避免阻塞主线程,这可能会导致应用的UI停止响应。

另外还需要注意避免死锁,如果NSOperationQueue中的某个操作依赖于当前线程的结果,那么使用waitUntilAllOperationAreFinished可能会导致死锁,因此需要确保操作之间没有相互依赖。

取消操作

在实际开发中,有时候我们需要取消一个已经添加到队列中的操作,例如,当用户中途取消了下载任务,或者某些操作在特定条件下变得不再必要时,取消操作可以帮助我们更好地管理资源和提高应用性能。

NSOperation提供了一个简单的方法cancel(),用于取消操作。执行完该方法后,该操作的isCancelled属性为被设置为true,我们可以在操作的执行过程中检查这个属性,以便提前结束任务。

let operation = BlockOperation {if !operation.isCancelled {print("Operation started")// 执行任务...}if operation.isCancelled {print("Operation was cancelled")return}print("Operation completed")
}operation.cancel()

有两点需要注意的:

  1. 取消操作不会立即终止操作的执行。操作本身需要定期检查isCancelled属性,并在适当的时候结束任务。
  2. 对于自定义的NSOperation子类,需要在适当的位置添加对isCancelled的检查,以支持取消操作。

如果需要取消NSOperation中的所有操作,可以使用cancelAllOperations()方法,这将取消队列种的所有操作,包括那些尚未开始的操作和正在执行的操作。

let queue = OperationQueue()let operation1 = BlockOperation {print("Operation 1 started")Thread.sleep(forTimeInterval: 2)print("Operation 1 completed")
}let operation2 = BlockOperation {print("Operation 2 started")Thread.sleep(forTimeInterval: 2)print("Operation 2 completed")
}queue.addOperations([operation1, operation2], waitUntilFinished: false)// 取消所有操作
queue.cancelAllOperations()

结语

通过本文,我们探讨了NSOperationNSOperationQueue的基本使用方法,操作优先级,操作依赖关系,最大并发数,阻塞线程以及取消操作。这些功能使得我们在iOS开发中能够灵活地管理并发任务和操作队列,从而提高应用的性能和响应性能。掌握这些技术,可以帮助我们更好地处理复杂的任务流程、优化资源使用、增强用户体验。

希望本文对你理解和应用NSOperationNSOperationQueue提供了有价值的帮助。如果你有任何疑问或需要进一步探讨,请随时留言交流。


http://www.ppmy.cn/embedded/109710.html

相关文章

iPhone 16 重磅发布!苹果发布会全程高能,带来8大震撼更新,苹果 AI 正式亮相|TodayAI

在刚刚结束的苹果 “It’s Glowtime” 发布会上,iPhone 16 无疑是最大的焦点,但苹果还公布了其他几项令人兴奋的更新,包括智能手表、AirPods 等产品的新功能和升级。以下是本次发布会的主要亮点。 1、iPhone 16 系列新增两大按钮 iPhone 16 …

java:ftpclient修改文件名

在 Java 中可以使用FTPClient类来实现对 FTP 服务器上文件的重命名操作。以下是示例代码: import org.apache.commons.net.ftp.FTPClient; import java.io.IOException; public class FTPRenameFile { public static void main(String[] args) { Stri…

M,儿母,阿母

英文字母M,音义通汉语:儿母。近单音“母”、“木”。 母,阴阳五行之阴阳之阴的典型代表,代表生化能力或生化之道,为成就者,象征生命缘起,代表生化之源。母,阴性,雌性&am…

【Java毕业设计】基于SpringBoot+Vue+uniapp的农产品商城系统

文章目录 一、系统架构1、后端:SpringBoot、Mybatis2、前端:Vue、ElementUI4、小程序:uniapp3、数据库:MySQL 二、系统功能三、系统展示1、小程序2、后台管理系统 一、系统架构 1、后端:SpringBoot、Mybatis 2、前端…

C# EF框架(Entity Framework)

EF框架(Entity Framework) EF概念 Entity Framework (EF) 是一个对象关系映射(Object-Relational Mapping,简称ORM)框架,它允许开发者使用面向对象的方式来处理数据库操作。EF 可以自动将数据库中的表映射…

数据结构:二叉树

一.二叉树(binary tree) 二叉树(binary tree)是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个…

【系统架构设计师】状态模式

状态模式(State Pattern)是行为设计模式的一种,它允许一个对象在其内部状态改变时改变它的行为。对象看起来像是改变了它的类。这种模式可以用于实现一些复杂的有限状态机,在不同的条件下改变对象的行为而无需修改对象本身。下面是一个关于状态模式的详细介绍,包括其概念、…

细致刨析JDBC ① 基础篇

目录 一、JDBC概述 1.JDBC的概念 ​编辑2.JDBC的核心组成 ① 接口规范: ② 实现规范: 二、JDBC快速入门 1.JDBC搭建步骤 三、核心API理解 1.注册驱动 2.Connection 3.Statement 4.PreparedStatement 5.ResultSet 四、基于Preparedment实现CRUD 1.查询单行单列 2.查询单行…