NSOperationQueue 中的屏障操作

问题描述 投票:0回答:5

我们如何使用

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

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

  • 无障碍操作应该能够并发执行。
  • 屏障操作应串行执行。

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

objective-c grand-central-dispatch nsoperationqueue barrier
5个回答
5
投票

创建一个

NSOperation
作为你的障碍,然后使用

- (void)addDependency:(NSOperation *)operation

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


4
投票

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

我创建了一个由

NSMutableArray
组成的
NSOperationQueue
,用作“队列的队列”。每次添加
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的评论,一种不同但相似的方法。该代码已经过测试,所以它已经成功了。但它有点陈旧(截至今天大约有 2 年;一些已弃用的方法使用)。此外,我质疑从

NSOperationQueue
继承是否是最好的路径,尽管它具有保持熟悉的优点。无论如何,如果您已经读到这里,可能值得一看。

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


3
投票

我认为不可能创建一个

NSOperation
对象来为您提供相同的功能,障碍更多地与队列的操作方式有关。

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

使用

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

(如果您知道一次只有一项关键作业,这听起来有点简单,但可能随时都有

n
关键作业在等待,这意味着跟踪作业提交的顺序,管理关键作业相对于其依赖作业的相对依赖性 - 某些关键作业可以等待其他作业,某些作业必须按照相对于其他作业的特定顺序执行...哎呀。)

也许可以通过设置并发作业最大值为 1 的

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


0
投票

换个方式...别伤害我。

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

0
投票

另一种方法:

NSOperarionQueue
/
OperationQueue
公开一个
underlyingQueue
属性,您可以将其设置为您自己的队列,并将屏障工作项像任何其他队列一样排队,并且这些应该阻止操作队列中的任何待处理工作项,直到障碍工作项已执行。

© www.soinside.com 2019 - 2024. All rights reserved.