NSOperationQueue 中的屏障操作

Posted

技术标签:

【中文标题】NSOperationQueue 中的屏障操作【英文标题】:Barrier operations in NSOperationQueue 【发布时间】:2014-04-25 22:14:16 【问题描述】:

我们如何使用NSOperationQueue 或任何基于NSOperationQueue 的用户定义数据结构来实现dispatch_barrier_async 的等效行为?

要求是,每当提交屏障操作时,它应该等到之前提交的所有非屏障操作完成执行并阻止之后提交的其他操作。

无障碍操作应该能够同时执行。 屏障操作应串行执行。

注意:不使用 GCD,因为它不提供(或至少难以)对操作的太多访问权限,例如取消单个操作等。

【问题讨论】:

如果 NSOperation 已经在运行,你也不能取消它。 @Cy-4AH 我不打算取消任何正在运行的 NSOperation,dispatch_barrier_async 也不会这样做。屏障函数将等待前面的操作完成。 【参考方案1】:

这或多或少是jeffamaphone 所说的,但我提出了一个gist,粗略地说,它应该按照你的要求去做。

我创建了NSOperationQueues 中的NSMutableArray,用作“队列队列”。每次添加 BarrierOperation 对象时,都会在末尾添加一个新的 suspended 操作队列。这将成为addingQueue,您可以在其中添加后续操作。

- (void)addOperation:(NSOperation *)op 
    @synchronized (self) 
        if ([op isKindOfClass:[BarrierOperation class]]) 
            [self addBarrierOperation:(id)op];
         else 
            [[self addingQueue] addOperation:op];
        
    


// call only from @synchronized block in -addOperation:
- (void)addBarrierOperation:(BarrierOperation *)barrierOp 
    [[self addingQueue] setSuspended:YES];

    for (NSOperation *op in [[self addingQueue] operations]) 
        [barrierOp addDependency:op];
    

    [[self addingQueue] addOperation:barrierOp];

    // if you are free to set barrierOp.completionBlock, you could skip popCallback and do that

    __block typeof(self) weakSelf = self;
    NSOperation *popCallback = [NSBlockOperation blockOperationWithBlock:^
        [weakSelf popQueue];
    ];
    [popCallback addDependency:barrierOp];
    [[self addingQueue] addOperation:popCallback];
    [[self addingQueue] setSuspended:NO];

    NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
    [opQueue setSuspended:YES];

    [_queueOfQueues addObject:opQueue]; // fresh empty queue to add to

当一个NSOperationQueue 完成时,它会弹出并开始运行下一个。

- (void)popQueue

    @synchronized (self) 
        NSAssert([_queueOfQueues count], @"should always be one to pop");
        [_queueOfQueues removeObjectAtIndex:0];

        if ([_queueOfQueues count]) 
            // first queue is always running, all others suspended
            [(NSOperationQueue *)_queueOfQueues[0] setSuspended:NO];
        
    

我可能错过了一些重要的事情。魔鬼在细节中。

这对我来说有点像家庭作业。如果是这样,请告诉我我得到了什么等级。 :)


附录:来自abhilash1912 的评论,一个不同但相似的approach。该代码已经测试,所以它已经成功了。但它有点陈旧(截至今天 2 年左右;一些已弃用的方法使用)。此外,我质疑从NSOperationQueue 继承是否是最好的路径,尽管它具有保持熟悉的优点。无论如何,如果您已经阅读了这么多,那么它可能值得一看。

如果您创建或发现了世界上最伟大的 BarrierQueue 类,请在 cmets 或其他方式中告诉我们,以便将其链接起来。

【讨论】:

不,这不是作业,所以给你评分。 :P。这是我公司项目要求的一部分,我想用 NSOperationQueue 替换 GCD 以获得更多的灵活性和可管理性。好吧,我有使用依赖项来实现它的想法,但我只是想确保这是最好的方法,并且需要一个更清洁的解决方案,而您的答案确实提供了。 :) 哦,等等,我明白了。撤回。 顺便说一句,我在这里找到了更多替代方案。 Daniel 在this 上的 cmets 在这种情况下似乎非常有用。【参考方案2】:

创建一个NSOperation作为你的障碍,然后use:

- (void)addDependency:(NSOperation *)operation

使该屏障操作依赖于您想要在它之前的所有操作。

【讨论】:

这只能满足我一半的要求。但有点明白了。谢谢。 :)【参考方案3】:

我认为不可能创建一个 NSOperation 对象来为您提供相同的功能,障碍更多地与队列的操作方式有关。

使用屏障和 NSOperations 的依赖机制之间的主要区别在于,在屏障的情况下,线程队列会一直等待,直到 所有 正在运行的并发操作完成,然后它会运行你的屏障块,同时确保提交的任何新块和等待的任何块在关键块通过之前不会运行

使用NSOperationQueue,不可能以强制执行适当屏障的方式设置队列:在您的关键NSOperation 之前添加到队列中的所有NSOperations 必须显式注册为与关键作业的依赖关系,一旦关键作业开始,您必须明确保护NSOperationQueue,以确保在关键作业完成之前没有其他客户端将作业推送到它;您可以通过添加关键作业作为后续操作的依赖项来保护队列。

(如果您知道一次只有一个关键作业,这听起来很容易,但可能随时都有n 关键作业在等待,这意味着跟踪提交作业的订单,管理关键作业相对于其依赖作业的相对依赖性——一些关键作业可以等待其他作业,有些必须以相对于其他作业的特定顺序执行……哎呀。)

可以通过设置NSOperationQueue 来获得这种级别的功能,同时最大并发作业为一个,但我认为这有点违背了这样做的目的。您还可以通过将NSOperationQueue 包装在保护“严格”提交的NSOperations 的外观对象中来完成这项工作。

【讨论】:

这或多或少是我的想法,但克莱的回答更具解释性。当然,它不能完全实现 GCD 对屏障操作的作用。无论如何,感谢您的精彩解释。 :)【参考方案4】:

只是另一种方式......不要伤害我。

Todo:保存原始完成和 self.maxConcurrentOperationCount = 1 在添加时将队列设置为串行。但应该在执行之前。

#import "NSOperationQueue+BarrierOperation.h"

@implementation NSOperationQueue (BarrierOperation)

- (void)addOperationAsBarrier:(NSOperation *)op

    //TODO: needs to save origin completion
    //    if (op.completionBlock)
    //    
    //        originBlock = op.completionBlock;
    //    

    NSOperationQueue* qInternal = [NSOperationQueue new];
    NSInteger oldMaxConcurrentOperationCount = self.maxConcurrentOperationCount;

    op.completionBlock = ^
        self.maxConcurrentOperationCount = oldMaxConcurrentOperationCount;
        NSLog(@"addOperationAsBarrier maxConcurrentOperationCount restored");
    ;

    [self addOperationWithBlock:^
        self.maxConcurrentOperationCount = 1;
        NSLog(@"addOperationAsBarrier maxConcurrentOperationCount = 1");
    ];

    [qInternal addOperationWithBlock:^
        NSLog(@"waitUntilAllOperationsAreFinished...");
        [self waitUntilAllOperationsAreFinished];
    ];

    NSLog(@"added OperationAsBarrier");
    [self addOperation:op];


@end

【讨论】:

以上是关于NSOperationQueue 中的屏障操作的主要内容,如果未能解决你的问题,请参考以下文章

多线程 NSOperation

gcd和NSOperationQueue区别

在单个 NSOperationQueue iOS 中管理多个操作,例如暂停/恢复单个操作

NSOperationQueue 和并发操作

什么是内存屏障

带有操作块的 NSOperationQueue 在 SWIFT 中无法按预期工作