首页 > 编程语言 >并发编程 - NSOperation&NSOperationQueue(多线程)

并发编程 - NSOperation&NSOperationQueue(多线程)

时间:2024-09-11 15:21:57浏览次数:3  
标签:... NSOperationQueue BlockOperation NSOperation let print 操作 多线程

​​​​​​​并发编程 - 概述-CSDN博客

并发编程 - GCD的任务和队列-CSDN博客

并发编程 - NSOperation&NSOperationQueue(多线程)-CSDN博客

并发编程 - NSThread-CSDN博客

引言

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

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

概念

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: - 创建OperationQueue
    private 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 = .normal
        operation2.queuePriority = .high
        operation3.queuePriority = .low
        operationQueue.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 = 2
        let 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()

结语

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

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

标签:...,NSOperationQueue,BlockOperation,NSOperation,let,print,操作,多线程
From: https://blog.csdn.net/weixin_39339407/article/details/141671217

相关文章

  • 【高级编程】认识Java多线程 代码举例三种创建线程的方式
    文章目录主线程创建线程方式1:Thread方式2:Runnable方式3:Callable进程:应用程序的执行实例,有独立的内存空间和系统资源线程:CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程多线程:如果在一个进程中同时运行了多个线程,用来完成不同的工......
  • C#笔记9 对线程Thread的万字解读 小小多线程直接拿下!
    上一条笔记有些潦草,这是因为昨天并没有很好的理解线程可以进行的操作。今天准备细化自己对这方面的理解和记录。来看看细节吧!环境:VS2022系统:windows10环境:.Net8.0以及.NetFrameWork4.7.2(winform)线程是什么?线程是什么?每个操作系统上运行的应用程序都是一个进程,一个......
  • day10-配置文件&日志&多线程
    一、配置文件1.1properties配置文件properties配置文件特点:1、都只能是键值对2、键不能重复3、文件后缀一般是.properties结尾的​Properties这是一个Map集合(键值对集合),但是我们一般不会当集合使用主要用来代表属性文件,通过Properties可以读写属性文件里的......
  • C++ 多线程详解:从基础到应用
    目录一、什么是多线程?二、C++中的多线程支持三、总结在现代应用中,多线程成为了提升程序性能的重要工具。特别是当我们希望充分利用多核CPU的计算能力时,C++提供了强大的多线程支持,可以并发地执行多个任务。今天,我们将通过易懂的讲解与实际的代码示例,帮助你掌握C+......
  • JAVA多线程-如何保证线程安全
    线程安全:指在多线程对一个共享资源同时进行操作时,所得到的结果都是一样的如何保证线程安全方法:要保证线程安全,就必须保证线程同步,保证线程的可见性,有序性,和原子性线程同步线程同步的含义和字面意思相反,同步其实是线程"排队"的意思,就是让线程按照一定的顺序执......
  • java多线程转换文件格式
    privatestaticfinalintTHREAD_COUNT=4;//线程数privatestaticfinalintBUFFER_SIZE=1024;//缓冲区大小/***多线程读取文件,转换文件编码格式4线程1Mb缓存**@paraminputFile输入文件Stringinput="E:/02code/web/test.txt"......
  • Java学习 - 多线程第二部分
    1.线程池1.1线程状态介绍当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢?Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:publi......
  • 多线程模拟叫号看病
    //普通号publicclassNormalThreadextendsThread{privateintnum=20;publicintgetNum(){returnnum;}publicvoidsetNum(intnum){this.num=num;}publicNormalThread(Stringname,intnum){super(......
  • 使用 Parallel 类进行多线程编码(下)
    2.Parallel.ForEach()的使用 从ForEach()这个名字可以看出该方法是用来遍历泛型集合的,新建一个ASP.NETCore Web应用的项目,如下:         在Index.cshtml.cs文件中增加一个UserInfo.cs的类,代码如下:publicclassUserInfo{publicint......
  • Numba最近邻插值(CPU+ GPU + Z轴切块 + XYZ轴切块 + 多线程)
    文章目录最近邻插值(加速方法)(1)scipy.ndimage.zoom(2)Numba-CPU加速(3)Numba-GPU加速(4)Numba-CPU加速(Z轴切块)(5)Numba-CPU加速(XYZ轴切块)(6)Numba-CPU加速(XYZ轴切块)+多线程输入数据插值倍数时耗scipy.ndimage.zoom(1024,1024,512)4172.16sNumba-CPU(1024,1024,512)456.58sN......