NSOperationQueue 串行 FIFO 队列

17

NSoperationQueue 对象的 maxConcurrentOperationCount 属性设置为 1,可以将其作为串行 FIFO 队列使用吗?

我注意到文档中提到...

对于最大并发操作数设置为 1 的队列,这相当于一个串行队列。但是,你不应该依赖操作对象的串行执行。

这是否意味着不能保证 FIFO 执行顺序?

3个回答

25
在大多数情况下,它将是先进先出(FIFO)。但是,您可以设置NSOperations之间的依赖关系,使早期提交的操作在队列中让其他操作跳过它,直到满足其依赖关系。
这种依赖关系管理是文档指出FIFO无法保证的原因。但是,如果不使用依赖项,则应该可以放心依赖它。
更新: NSOperation还具有queuePriority属性,这也可能导致操作以非FIFO顺序执行。没有待处理依赖项的最高优先级操作将始终首先执行。
NSOperation子类还可以重写-isReady方法,这可能会导致它返回到队列中。
因此,您的队列上的执行被保证为串行的,即在此队列中不会同时运行超过一个操作。但是Apple不能保证FIFO;这取决于您放入其中的操作正在做什么。

执行顺序还取决于操作队列的优先级。 - JeremyP
那么,如果我的操作都具有相同的优先级,没有依赖性,并且仅依赖于isReady的超类实现(我没有覆盖),那么它应该会产生一个FIFO队列? - Barjavel
主队列绑定到单个线程,因此本质上只能一次运行一个操作。因此,它的maxConcurrentOperationCount已经是1。将其再次设置为1不会造成任何问题。 - BJ Homer
每次将操作添加到队列中都会获得一个新线程(可能有数百个),大多数线程会等待信号量,这种方式似乎不太有效率来执行串行队列。 - Symmetric
这是你BJ Homer吗?http://www.atsloginc.com/nsoperationqueue-serial-fifo-queue/。看起来确实像更多的代码和类似的东西。 - Matthew Ferguson
显示剩余3条评论

14

根据文档所述,队列不是先进先出的。如果您确保任何新操作都依赖于队列中添加的最后一个操作,并且它一次只能运行单个操作,则可以使其严格遵循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。队列优先级与FIFO无关,而是与作业调度有关。

编辑:根据下面的评论,如果仅检查数量时挂起队列也是不够的。我认为这种形式是可以的(经过测试,这不会创建竞态条件并且不会崩溃)。

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


这种解决方案存在问题。我一直在使用它。偶尔,在检查queue.operations.count之后,队列的lastObject会消失。这很少见,但确实发生了。而且我不确定该如何解决它。 - Darren Black
好的,我没有看到这一点,但是是的:如果在队列运行时检查操作计数,可能会在我们进入if条件时结束。我认为修改后的形式更好、更安全:(这里的测试不会崩溃,而尝试挂起队列的任何尝试都不起作用(未显示)。) - Daniel
这就是我想到的解决方案:创建一个本地变量,防止NSOperation被释放。 - Darren Black
在添加所有操作时,暂停队列。 - malhal
@malhal 这样行吗?我认为当您暂停操作队列时,它不会启动任何新操作,但正在执行的任何操作都将继续。如果是这种情况,那么暂停队列不会阻止最后一个操作在 queue.operations.count 检查之后完成。因此问题仍然存在。 - Gordonium

-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直到您真正准备好了 在这些情况下,您需要在队列内的操作之间添加依赖关系

来自苹果文档的说明:这相当于串行队列。但是,您永远不应该依赖于操作对象的串行执行。操作的可用性更改可能会更改结果的执行顺序


你能解释一下为什么依赖关系是必要的吗? - BJ Homer
你在示例中的队列没有像OP指定的maxConcurrentOperationCount = 1。如果设置了这个值,它还会发生吗? - BJ Homer
在这个例子中不会出现这种情况,但考虑一个更大的操作,在这种情况下您需要子类化NSOperation并覆盖isReady方法,而您的isReady实现可能会返回no。请考虑以下苹果文档中的注释:这相当于一个串行队列。然而,您永远不应该依赖操作对象的串行执行。操作的可用性的变化可以改变其执行顺序 - Omar Abdelhafith
好的,那是一个有效的例子。但是你原来的回答完全无效;NSInvocationOperation本身并没有做任何会导致非FIFO执行顺序的事情。 - BJ Homer
是的,你说得对,我的例子确实不好,也许我应该编辑一下并添加更多的解释 :) - Omar Abdelhafith

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