我们如何使用或基于任何用户定义的数据结构来实现dispatch_barrier_async
等效的行为?NSOperationQueue
NSOperationQueue
要求是,每当提交屏障操作时,它应该等到之前提交的所有非屏障操作完成执行并阻止之后提交的其他操作。
- 非屏障操作应该能够同时执行。
- 屏障操作应该串行执行。
注意:不使用GCD,因为它不提供(或至少难以)对操作的太多访问,例如取消单个操作等。
我们如何使用或基于任何用户定义的数据结构来实现dispatch_barrier_async
等效的行为?NSOperationQueue
NSOperationQueue
要求是,每当提交屏障操作时,它应该等到之前提交的所有非屏障操作完成执行并阻止之后提交的其他操作。
注意:不使用GCD,因为它不提供(或至少难以)对操作的太多访问,例如取消单个操作等。
这或多或少是jeffamaphone所说的,但我提出了一个要点,粗略地说,应该按照你的要求去做。
我创建了一个NSMutableArray
of NSOperationQueue
s,它充当“队列队列”。每次添加BarrierOperation
对象时,都会在最后添加一个新的挂起操作队列。这将成为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
之前添加到队列中的所有 sNSOperation
必须显式注册为关键作业的依赖项,并且一旦关键作业具有开始时,您必须明确保护NSOperationQueue
以确保在关键作业完成之前没有其他客户端将作业推送到它;您可以通过添加关键作业作为后续操作的依赖项来保护队列。
(在您知道一次只有一个关键作业的情况下,这听起来很容易,但可能随时都有n
关键作业在等待,这意味着跟踪提交作业的订单,管理关键作业相对于它们的依赖作业——一些关键作业可以等待其他作业,有些必须以相对于其他作业的特定顺序执行……哎呀。)
NSOperationQueue
通过设置一个并发作业最大值为一个,可能可以获得这种级别的功能,但我认为这有点违背了这样做的目的。您还可以通过将一个包装在一个保护“关键”提交NSOperationQueue
的外观对象中来完成这项工作。NSOperations
只是另一种方式...不要伤害我。
待办事项:保存原始完成和 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