我们如何使用
dispatch_barrier_async
或任何基于 NSOperationQueue
的用户定义数据结构来实现 NSOperationQueue
的等效行为?
要求是,每当提交屏障操作时,它应该等待,直到之前提交的所有非屏障操作完成执行,并阻止此后提交的其他操作。
注意:不使用GCD,因为它不提供(或至少困难)对操作的太多访问,例如取消单个操作等。
这或多或少是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 类,请在评论或其他方式中告诉我们,以便将其链接起来。
我认为不可能创建一个
NSOperation
对象来为您提供相同的功能,障碍更多地与队列的操作方式有关。
使用屏障和 NSOperations 的依赖机制之间的主要区别是,在屏障的情况下,线程队列会等待直到所有运行的并发操作完成,然后它运行你的屏障块,同时确保任何提交的新块和等待的任何块不要运行,直到关键块通过。
使用
NSOperationQueue
,不可能以强制执行适当屏障的方式设置队列:在关键 NSOperation
之前添加到队列中的所有 NSOperation
必须显式注册为依赖项关键作业,一旦关键作业开始,您必须显式保护 NSOperationQueue
以确保在关键作业完成之前没有其他客户端将作业推送到其上;您可以通过将关键作业添加为后续操作的依赖项来保护队列。
(如果您知道一次只有一项关键作业,这听起来有点简单,但可能随时都有
n
关键作业在等待,这意味着跟踪作业提交的顺序,管理关键作业相对于其依赖作业的相对依赖性 - 某些关键作业可以等待其他作业,某些作业必须按照相对于其他作业的特定顺序执行...哎呀。)
也许可以通过设置并发作业最大值为 1 的
NSOperationQueue
来获得这种级别的功能,但我认为这有点违背了这样做的目的。 您还可以通过将 NSOperationQueue
包装在外观对象中来保护“严格”提交的 NSOperations
来完成这项工作。
换个方式...别伤害我。
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
另一种方法:
NSOperarionQueue
/OperationQueue
公开一个 underlyingQueue
属性,您可以将其设置为您自己的队列,并将屏障工作项像任何其他队列一样排队,并且这些应该阻止操作队列中的任何待处理工作项,直到障碍工作项已执行。