NSOperation与Grand Central Dispatch的区别

490

我正在学习iOS并发编程。到目前为止,我已经阅读了关于NSOperation/NSOperationQueueGCD的内容。使用NSOperationQueueGCD的原因是什么?它们之间的关系不太清楚,希望能得到一些反馈!

听起来GCDNSOperationQueue都可以将NSThreads的创建从用户中抽象出来。然而,两种方法之间的关系对我来说并不清晰,因此非常感谢任何反馈!


11
好问题,点赞!对结果很感兴趣。目前为止,我只看到最大公约数可以轻松地分配到CPU核心上,因此成为了“新的热门玩意儿”。 - Till
3
一些相关的讨论可以在这个问题中找到:为什么我应该选择GCD而不是NSOperation和块来进行高级应用程序开发? - Brad Larson
1
https://cocoacasts.com/choosing-between-nsoperation-and-grand-central-dispatch/ - Masih
9个回答

543

GCD 是一个基于 C 语言的低级 API,可以非常简单地使用任务并发模型。 NSOperationNSOperationQueue 是 Objective-C 类,它们也做了类似的事情。 NSOperation 首先被介绍,但是从10.5iOS 2开始,NSOperationQueue及其相关类已经内部实现了GCD

通常情况下,您应该使用最适合您需求的最高级别的抽象。这意味着通常应该使用NSOperationQueue而不是GCD,除非您需要做一些NSOperationQueue不支持的事情。

请注意,NSOperationQueue 不是 GCD 的“简化”版;事实上,有很多东西您可以用NSOperationQueue非常简单地完成,但使用纯粹的GCD会很困难。(例如带宽受限队列,每次只运行 N 次操作; 在操作之间建立依赖关系。这两者都对NSOperation来说非常简单,但对于GCD则很困难。)苹果已经利用了 GCD 做出了很多努力,创建了一个非常好的面向对象的 API,即NSOperation,请利用他们的工作,除非您有理由不这样做。

警告: 另一方面,如果您只需要发送一个块,并且不需要NSOperationQueue提供的任何其他功能,则使用 GCD 没有任何问题。只要确保它是适合此任务的正确工具。


1
NSOperation,具体来说是一个抽象类。 - Roshan
3
@Sandy实际上相反,GCD被NSOperation使用(至少在较新的iOS和OS X版本中)。 - garrettmoon
1
@BJ Homer 我们可以将任务添加到串行调度队列中以实现依赖关系。请解释操作队列相对于此的优势。 - Raj Aggrawal
3
@RajAggrawal 是的,那个方法可以行得通...但是你将会被限制在串行队列中。NSOperation 可以做到"在其他三个操作完成之后执行此操作,但仍与正在进行的所有其他操作并发地进行"。操作依赖关系甚至可以存在于不同队列上的操作之间。大多数人可能不需要这样做,但如果你需要,NSOperation 将是一个更好的选择。 - BJ Homer

382

根据我对一个相关问题的回答,我要不同意BJ的意见,并建议您首先查看GCD而不是NSOperation/NSOperationQueue,除非后者提供了GCD无法满足的功能。

GCD出现之前,我在应用程序中使用了大量的NSOperations / NSOperationQueues来管理并发。然而,自从我开始经常使用GCD后,我几乎完全用块和分派队列替换了NSOperations和NSOperationQueues。这是因为我在实践中使用这两种技术的方式以及我对它们进行的剖析。

首先,当使用NSOperations和NSOperationQueues时,存在相当多的开销。这些都是Cocoa对象,需要进行分配和解除分配。我编写的一个iOS应用程序以60 FPS渲染3D场景时,我使用NSOperations封装每个渲染帧。当我对此进行剖析时,创建和拆除这些NSOperations占用了正在运行的应用程序的相当一部分CPU周期,并且会拖慢速度。我用简单的块和GCD串行队列替换了它们,这种开销消失了,导致渲染性能明显提高。这不是我注意到使用NSOperations时开销的唯一地方,在Mac和iOS上都看到过这种情况。

其次,当使用NSOperations时,难以与块式分派代码的优雅相匹配。将几行代码包装在块中并将其分派到串行或并发队列上,非常方便,而创建自定义NSOperation或NSInvocationOperation来执行此操作需要更多的支持代码。我知道您可以使用NSBlockOperation,但还不如直接将其分派到GCD。将此代码与应用程序中相关处理内联包装,可以使代码组织更好,而不是使用单独的方法或自定义NSOperations封装这些任务。

NSOperations和NSOperationQueues仍然具有非常好的用途。GCD没有真正的依赖关系概念,而NSOperationQueues可以设置相当复杂的依赖图。我在一些情况下使用NSOperationQueues。

总的来说,虽然我通常主张使用最高级别的抽象来完成任务,但这是一个例外情况,我主张使用更低级别的GCD API。在我与iOS和Mac开发者谈论此事时,绝大多数人选择使用GCD而不是NSOperations,除非他们的目标操作系统版本不支持它(早于iOS 4.0和Snow Leopard)。


20
我稍微有些不同意,我经常使用普通的GCD。但是我认为你在这个回答中对NSBlockOperation的重要性折扣过大了。所有NSOperationQueue的好处(依赖性、可调试性等)也适用于块操作。 - BJ Homer
4
@BJHomer - 我认为在我这种情况下,避免使用NSBlockOperation更多是出于个人偏好的原因,尽管在看到使用NSOperations会拖慢一些应用程序的开销后,我总体上都回避使用NSOperations。如果我要使用块,则倾向于全部使用GCD,只有在需要依赖支持时才极少使用NSBlockOperation。 - Brad Larson
1
+1,感谢这个分析。苹果似乎在倡导两者(例如WWDC 2012年关于并发UI的会话),因此非常感谢。 - orip
1
@VolureDarkAngel - GCD在处理这样的调度时非常快。在您所描述的情况下,它不应该成为瓶颈,除非由于缓慢的I/O访问或类似问题而将一堆更新备份到队列中。但这里可能不是这种情况。 - Brad Larson
1
@asma22 - 常见的情况是有一些可以分块计算的操作,但某个阶段的最终计算可能需要前几个阶段的结果。在这种情况下,您可以使后面的操作依赖于前面的操作,调度将被管理,以确保所有先前的操作都在最后一个操作运行之前完成。 - Brad Larson
显示剩余13条评论

131

GCD是一个基于C语言的低级API。
NSOperationNSOperationQueue是Objective-C类。
NSOperationQueue是对GCD的Objective-C封装。 如果你正在使用NSOperation,那么你就隐式地使用了Grand Central Dispatch

GCD相较于NSOperation的优势:
i. 实现
对于GCD,其实现非常轻量级
NSOperationQueue则是复杂而且重量级的

NSOperation相较于GCD的优势:

i. 对操作的控制
你可以暂停、取消、恢复一个NSOperation

ii. 依赖关系
你可以在两个NSOperations之间建立依赖关系
只有当它的所有依赖项状态为已完成时,该操作才会开始。

iii. 操作或操作队列的状态
可以监视操作或操作队列的状态。 准备好,执行或完成

iv. 最大操作数
你可以指定同时运行的最大排队操作数

何时选择GCDNSOperation
当你希望更多地控制队列(如上所述)时,请使用NSOperation 而对于简单的情况,如果你只想在后台做一些工作并且额外工作非常少,请使用GCD

参考:
https://cocoacasts.com/choosing-between-nsoperation-and-grand-central-dispatch/ http://iosinfopot.blogspot.in/2015/08/nsthread-vs-gcd-vs-nsoperationqueue.html

http://nshipster.com/nsoperation/ 这篇文章介绍了iOS开发中的NSOperation类,它是一个抽象类,用于封装并执行异步任务。与GCD相比,NSOperation提供了更多的控制和可定制化的选项,例如设置依赖关系和取消操作。此外,NSOperation也可以与NSOperationQueue协同工作以实现更高效和可伸缩的任务调度。

正如所说,NSOperationQueue中可以指定操作的最大数量,那么GCD中操作(调度队列)的最大数量是多少?假设我有一个项目,那么我可以执行多少个操作(调度队列),或者是否存在我们可以执行的最大限制。 - Roshan Sah
这取决于系统条件,这里有详细信息:https://dev59.com/L2Up5IYBdhLWcg3w3KXe - Sangram Shivankar
我们也可以使用DispatchWorkItem来取消GCD中的任务,同时我们还能够挂起和恢复任务。 - garg
@Ankitgarg 在 DispatchWorkItem 上调用 cancel 将会停止尚未运行的任务,但不会中止已经在执行的任务。那么如何暂停/恢复 DispatchWorkItem 呢? - abhimuralidharan
1
这是一种简单而清晰的解释方式。 - Jayprakash Dubey

34

选择 NSOperation 而不是 GCD 的另一个原因是 NSOperation 具有取消机制。例如,像 500px 这样显示数十张照片的应用程序,使用 NSOperation 可以在滚动表格视图或集合视图时取消不可见图像单元的请求,这可以大大提高应用程序性能并减少内存占用。GCD 无法轻松支持此功能。

此外,使用 NSOperation,KVO 是可能的。

这里有一篇来自Eschaton值得一读的文章。


4
值得注意的是,如果你要取消的是加载图片的网络操作,那么你不需要使用 NSOperation,因为 NSURLSessionTask.cancelNSURLSession.invalidateAndCancel 提供了这个功能。通常情况下,NSURLSession 提供了一些 NSOperationQueue 的功能,因为 NSURLSessionTask 提供了一些 NSOperation 的功能。 - algal
如此解释:(https://dev59.com/ZWEh5IYBdhLWcg3w32xG)据说NSURLSession使用NSOperationQueue作为构建块。 - kalan nawarathne

33

GCD的级别确实比NSOperationQueue低,它的主要优点是其实现非常轻量级,并且专注于无锁算法和性能。

NSOperationQueue提供了一些GCD不可用的功能,但这些功能的代价是相当高的。NSOperationQueue的实现复杂而重量级,涉及大量的锁定,并且仅以非常最小的方式在内部使用GCD。

如果你需要NSOperationQueue提供的功能,请务必使用它。但是,如果GCD对你的需求足够,我建议直接使用它,以获得更好的性能、显著降低CPU和功率成本以及更多的灵活性。


24

NSQueueOperations和GCD都允许在后台的独立线程上执行重型计算任务,从而释放UI应用程序主线程。

根据先前的帖子,我们看到NSOperations具有addDependency,因此您可以按顺序将操作排队一个接一个地执行。

但我也了解到,您可以使用GCD串行队列创建其运行操作队列,使用dispatch_queue_create。这将允许以顺序方式依次运行一组操作。

NSQueueOperation优势超过GCD:

  1. 它允许添加依赖项并允许您删除依赖项,因此您可以使用依赖关系运行一个事务并发运行另一个事务,而GCD不允许以这种方式运行。

  2. 如果处于队列中,可以轻松取消操作,并且如果正在运行,则可以停止它。

  3. 您可以定义最大并发操作数量。

  4. 您可以暂停在队列中的操作

  5. 您可以查找队列中有多少待处理操作。


6

GCD非常易于使用——如果您希望在后台执行某项任务,只需编写代码并将其分派到后台队列即可。使用NSOperation完成相同的任务需要大量额外的工作。

NSOperation的优点是(a)您拥有一个真正的对象可以向其发送消息,以及(b)您可以取消一个NSOperation。这不是微不足道的。您需要子类化NSOperation,正确编写代码以使取消并正确完成任务都能正确工作。因此,对于简单的任务,您可以使用GCD,而对于更复杂的任务,则需要创建NSOperation的子类。(有NSInvocationOperation和NSBlockOperation等子类,但它们所做的一切都可以使用GCD更轻松地实现,因此没有充分的理由使用它们)。


3
NSOperations只是建立在Grand Central Dispatch之上的API。因此,当您使用NSOperations时,实际上仍在使用Grand Central Dispatch。只不过NSOperations提供了一些您可能喜欢的高级功能。您可以使某些操作依赖于其他操作,在提交项目后重新排序队列等等。事实上,ImageGrabber已经在使用NSOperations和操作队列!ASIHTTPRequest在其内部使用它们,并且您可以配置它使用的操作队列以获得不同的行为。那么应该使用哪个呢?无论哪个对您的应用程序有意义。对于这个应用程序来说,很简单,我们直接使用Grand Central Dispatch,不需要NSOperation的高级特性。但是,如果您的应用程序需要它们,请随意使用!

0

我同意@Sangram和其他答案,但想补充几点。如果我错了,请纠正我。

我认为现在@Sangram的回答中的前两个点不再有效 (i.操作控制ii.依赖关系)。我们也可以使用GCD来实现这两点。试图通过代码解释(不要关注代码质量,仅供参考)

func methodsOfGCD() {
    
    let concurrentQueue = DispatchQueue.init(label: "MyQueue", qos: .background, attributes: .concurrent)
    
    
    //We can suspend and resume Like this
    concurrentQueue.suspend()
    concurrentQueue.resume()
    
    //We can cancel using DispatchWorkItem
    let workItem = DispatchWorkItem {
        print("Do something")
    }
    concurrentQueue.async(execute: workItem)
    workItem.cancel()
    
    //Cam add dependency like this.
    //Operation 1
    concurrentQueue.async(flags: .barrier) {
        print("Operation1")
    }

    //Operation 2
    concurrentQueue.async(flags: .barrier) {
        print("Operation2")
    }

    //Operation 3.
    //Operation 3 have dependency on Operation1 and Operation2. Once 1 and 2 will finish will execute Operation 3. Here operation queue work as a serial queue.
    concurrentQueue.async(flags: .barrier) {
        print("Operation3")

    }

}

完成 Objective-C 后,也许你可以解释一下 let workItem = DispatchWorkItem - Ol Sen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接