NSOperationQueue 串行 FIFO 队列

Posted

技术标签:

【中文标题】NSOperationQueue 串行 FIFO 队列【英文标题】:NSOperationQueue serial FIFO queue 【发布时间】:2012-06-12 12:15:08 【问题描述】:

是否可以通过将maxConcurrentOperationCount 设置为1 来将NSoperationQueue 对象用作串行FIFO 队列?

我注意到docs 状态...

对于最大并发操作数设置为 1 的队列,这等同于串行队列。但是,您永远不应该依赖操作对象的串行执行。

这是否意味着无法保证 FIFO 的执行?

【问题讨论】:

【参考方案1】:

在大多数情况下,它将是 FIFO。但是,您可以设置 NSOperations 之间的依赖关系,以便提前提交的操作会让队列中的其他操作通过它,直到满足其依赖关系。

这种依赖管理是文档指出不能保证 FIFO-ness 的原因。但是,如果您不使用依赖项,则依赖它应该没问题。

更新: NSOperation 还有一个queuePriority 属性,它也可以导致操作以非FIFO 顺序执行。没有挂起依赖关系的最高优先级操作将始终首先执行。

NSOperation 子类也可能会覆盖-isReady,这可能会导致它移回队列中。

因此,您的队列上的执行保证是串行,因为在此队列中一次不会运行超过一个操作。但是苹果不能保证先进先出;这取决于你对你投入的操作做了什么。

【讨论】:

执行顺序也取决于操作的队列优先级。 所以如果我的操作都具有相同的优先级,没有依赖关系并且只依赖于isReady 的超类实现(我没有覆盖)它应该导致FIFO队列? 正确; NSOperationQueue 只有在你给它一个理由的情况下才会重新排序。如果你不做任何这些事情,它只是按顺序进行。 如果我有一堆操作要在主队列中串行执行怎么办?将 mainqueue 的 maxConcurrentOperationCount 设置为 1 可能会导致界面不稳定。 主队列绑定到单个线程,因此本质上一次只能运行一个操作。所以它的 maxConcurrentOperationCount 已经 1。再次将其设置为 1 不会导致任何问题。【参考方案2】:

队列不是文档中提到的 FIFO。如果您确保任何新操作都依赖于添加到队列中的最后一个操作并且一次只能运行一个操作,则可以使其严格 FIFO。 Omar 解决方案是正确的,但更一般地,您可以执行以下操作:

NSOperationQueue* queue = [[ NSOperationQueue alloc ] init];
queue.maxConcurrentOperationCount = 1;

NSOperation* someOperation = [ NSBlockOperation blockOperationWithBlock:^(void)  NSLog(@"Done."); ];

if ( queue.operations.count != 0 )
    [ someOperation addDependency: queue.operations.lastObject ];

这是因为 queue.operations 是一个数组:无论你添加什么都不会重新排序(例如,它不是 NSSet)。您也可以简单地向您的 NSOperationQueue 添加一个类别:

@interface NSOperationQueue (FIFOQueue)
- (void) addOperationAfterLast:(NSOperation *)op;
@end

@implementation NSOperationQueue (FIFOQueue)

- (void) addOperationAfterLast:(NSOperation *)op

    if ( self.maxConcurrentOperationCount != 1)
        self.maxConcurrentOperationCount = 1;

    NSOperation* lastOp = self.operations.lastObject;
    if ( lastOp != nil )
        [ op addDependency: lastOp ];

    [ self addOperation:op];


@end

并使用 [queue addOperationAfterLast:myOperation]。 queuePriority 与 FIFO 无关,与作业调度有关。

编辑:在下面的评论之后,如果检查计数也不够,则暂停队列。我相信这种形式很好(经过测试,这不会产生竞争条件并且不会崩溃)。

一些信息:https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperationQueue_class/#//apple_ref/occ/instp/NSOperationQueue/suspended

【讨论】:

这种解决方案有问题。我一直在使用它。有时,队列的 lastObject 会在检查 queue.operations.count 后消失。这是罕见的,但它会发生。而且我不确定如何解决它。 好点我没有看到,但是是的:如果在队列运行时检查了 operations.count,它可能会在我们进入 if 条件时结束。我相信编辑后的表单更好更安全:(此处的测试不会崩溃,而暂停队列的尝试不起作用(未显示)。 我也是这样解决的;创建一个局部变量,防止 NSOperation 被释放。 在添加所有操作时暂停队列。 @malhal 这行得通吗?我认为当您暂停操作队列时,它不会启动任何新操作,但正在执行的任何操作都会继续。如果是这种情况,那么暂停队列不会阻止最后一个操作在queue.operations.count 检查之后完成吗?所以问题仍然存在。【参考方案3】:

使用 nsInvocationopration 创建一个简单的 FIFO 您需要将一项操作设置为依赖于另一项操作 使用 addDependency: 方法

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *oper1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSth:) object:@"1"];

NSInvocationOperation *oper2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSth:) object:@"2"];
NSInvocationOperation *oper3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSth:) object:@"3"];

[oper2 addDependency:oper1];
[oper3 addDependency:oper2];
//oper3 depends on oper2 wich depends on oper1
//order of execution will ber oper1->oper2->oper3

//Changing the oreder will not change the result
[queue addOperation:oper2];
[queue addOperation:oper3];
[queue addOperation:oper1];


- (void) doSth:(NSString*)str

    NSLog(str); //log will be 1 2 3
    //When you remove the addDependency calls, the logging result that i got where
    //different between consecutive runs i got the following
    //NSLog(str); //log will be 2 1 3
    //NSLog(str); //log will be 3 1 2

注意:如果您使用的是 NSInvocationOperation,那么将 maxConcurrentOperationCount 设置为 1 很可能对您有用,因为您无法编辑 isReady

但如果您打算创建自己的 NSOperation 子类,maxConcurrentOperationCount = 1 将不是一个好的解决方案

由于在 NSOperation 派生类中,您可以覆盖 isReady 函数并返回 no,(想象一些需要等待来自服务器的一些数据才能正常运行的操作)在这些情况下,您将返回 isReady no 直到你真的准备好了 在这些情况下,您需要在队列中的operations 之间添加dependencies

来自苹果文档这相当于一个串行队列。但是,您永远不应该依赖操作对象的串行执行。操作准备情况的变化可能会改变生成的执行顺序

【讨论】:

你能解释一下为什么依赖是必要的吗? 您在示例中的队列没有 maxConcurrentOperationCount = 1 像指定的 OP 一样。这套设备还会出现这种情况吗? 在这个例子中它不会,但是考虑一个相当大的操作,你需要继承 NSOperation 并覆盖 isReady,并且你的 isReady 实现可能会返回 no,请考虑苹果文档中的以下评论 这相当于一个串行队列。但是,您永远不应该依赖操作对象的串行执行。操作准备情况的变化可能会改变生成的执行顺序 好的,这是一个有效的例子。但是您的原始答案完全无效; NSInvocationOperation 本身不会做任何会导致非 FIFO 执行顺序的事情。 是的,你是对的,我的例子确实很糟糕,也许我应该编辑它并添加更多解释:)

以上是关于NSOperationQueue 串行 FIFO 队列的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发-91GCD的同步异步串行并行NSOperation和NSOperationQueue一级用dispatch_once实现单例(转载)

GCD与NSOperationQueue

伟大的GCD和NSOperationQueue

gcd和NSOperationQueue区别

NSOperationNSOperationQueue(II)

Verilog实现同步FIFO