NSOperation - 强制操作动态等待其他操作

时间:2012-12-07 11:43:01

标签: objective-c ios nsthread nsoperation nsoperationqueue

我正在尝试实现一个操作队列,我有以下场景:

NSOperation A
NSOperation B
NSOperation C
NSOperation D
NSOperationQueue queue

我开始将A添加到queue

在执行A期间,我需要从B获取一些数据,在A返回我需要的内容之前,我无法继续使用B

B取决于CC取决于DNSOperation也会出现同样的情况。

为了管理这一点,我在每个NSOperation *operation; //This can be A, B, C, D or any other NSOperation [self setQueuePriority:NSOperationQueuePriorityVeryLow]; //Set the current NSOperation with low priority [queue addOperation: operation]; //Add the operation that I want to the queue while(!operation.isFinished && !self.isCancelled){} //I need to wait the operation that I depend before moving on with the current operation [self setQueuePriority:NSOperationQueuePriorityNormal]; //After the while, the other operation finished so I return my priority to normal and continue if(self.isCancelled){ //If I get out of the while because the current operation was cancelled I also cancel the other operation. [operation cancel]; } 都有这个代码:

NSOperations

我的问题是,当我有3或4 while(!operacao.isFinished && !self.isCancelled){}之类的东西等待并执行[operation start]时,我的代码就会冻结,因为对我来说重要的NSOperation不会被执行,即使它有更高的优先权。

我尝试了什么

  • 在执行期间添加依赖项但由于我的NSOperation已经在运行,我似乎没有任何效果。

  • 我可以执行while(!operacao.isFinished && !self.isCancelled){[NSThread sleepForTimeInterval:0.001];}的操作,而不是将操作添加到队列中。它有效,但是取消当前的操作也会取消我开始的其他操作吗?

  • 我可以做{{1}}之类的事情。它有效,但这是正确的方法吗?也许有更好的解决方案。

在这种情况下我怎么能保证我想要的操作会运行而其他人会在后台等待?解决这个问题的正确方法是什么?

如果有人质疑我为什么在开始我的队列之前不添加依赖项,因为只有在某些条件为真时操作才需要另一个操作。我会知道我是否只在执行期间需要其他操作。

感谢您的时间。

7 个答案:

答案 0 :(得分:37)

这里有两个有人为的例子的想法。我只使用了两个操作,但您可以将概念扩展为任意数字和/或根据需要嵌套它们。

示例1:使用Grand Central Dispatch

GCD提供轻量级“调度组”,允许您明确地命令任务,然后等待完成。在这种情况下,AlphaOperation创建一个组并输入它,然后启动BetaOperation,其completionBlock导致该组被留下。当你调用dispatch_group_wait时,当前线程会阻塞,直到进入组的次数等于离开它的次数(很像保留计数)。在任何可能长时间运行的任务之后,不要忘记检查操作的isCancelled状态。

@interface BetaOperation : NSOperation
@end
@implementation BetaOperation
- (void)main
{
    NSLog(@"beta operation finishing");
}
@end

@interface AlphaOperation : NSOperation
@end
@implementation AlphaOperation
- (void)main
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);

    BetaOperation *betaOperation = [[BetaOperation alloc] init];
    betaOperation.completionBlock = ^{
        dispatch_group_leave(group);
    };

    [betaOperation start];

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    if ([self isCancelled])
        return;

    NSLog(@"alpha operation finishing");
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    dispatch_async(dispatch_get_main_queue(), ^{
        AlphaOperation *operation = [[AlphaOperation alloc] init];
        [operation start];
    });

    return YES;
}

@end

示例2:使用本地NSOperationQueue

由于您已经在进行工作操作,因此另一个选择是将队列创建为AlphaOperation的属性,然后添加BetaOperation并在队列上调用waitUntilAllOperationsAreFinished。这样做的另一个好处是,只需覆盖cancel方法,即可在取消AlphaOperation时轻松取消队列操作。

@interface BetaOperation : NSOperation
@end
@implementation BetaOperation
- (void)main
{
    NSLog(@"beta operation finishing");
}
@end

@interface AlphaOperation : NSOperation
@property (strong) NSOperationQueue *queue;
@end
@implementation AlphaOperation
- (void)main
{
    self.queue = [[NSOperationQueue alloc] init];

    BetaOperation *betaOperation = [[BetaOperation alloc] init];
    [self.queue addOperation:betaOperation];
    [self.queue waitUntilAllOperationsAreFinished];

    if ([self isCancelled])
        return;

    NSLog(@"alpha operation finishing");
}

- (void)cancel
{
    [super cancel];

    [self.queue cancelAllOperations];
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    dispatch_async(dispatch_get_main_queue(), ^{
        AlphaOperation *operation = [[AlphaOperation alloc] init];
        [operation start];
    });

    return YES;
}

@end

答案 1 :(得分:6)

一种方法是从操作类外部管理它,即。在创建A / B / C / D时正确设置操作依赖关系。

步骤:(在创建这些操作的方法中)

1)创建操作A

2)如果操作B提供的数据不可用,则创建操作B,并使操作A依赖于操作B.像operationA.addDependency(operationB);

这样的东西

3)。对C和D重复步骤2(即B取决于C,C取决于D,如果需要)

4)将操作添加到队列中。队列将基于依赖性执行,即。 D,C,B,A。

答案 2 :(得分:3)

尝试使用setCompletionBlock:,如下所示:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *operationA;
NSOperation *operationB;

//... initialize operationA and operationB however you please ...

[operationA setCompletionBlock:^{
    if ([operationA satisfiesSomeCriteria]) {
        [queue addOperation:operationB];
    }
}];

[queue addOperation:operationA];

在操作上设置完成块时,将在操作的主要任务完成或取消后执行。因此,操作执行的工作结果可用,以便您可以决定是否应将下一个操作添加到队列中。

答案 3 :(得分:2)

所以基本上你只需要确保第一个在开始下一个之前完成? NSOperationQueue将并行运行,除非你告诉它不要。您可以在操作队列上调用setMaxConcurrentOperationCount:并将其设置为1,基本上将其转换为一次只运行一个操作的串行队列。

答案 4 :(得分:2)

一旦NSOperation成为其主要方法,你必须经历它。没有暂停状态,只有完成或取消。

我将在操作A上实现NSCopying,它将整个状态复制到一个新实例中。您将拥有一个委托方法或块,它能够通知此操作无法通过,因为它缺少操作B的信息。

所以这个过程就是这样的:

  • 创建操作A,设置委托
  • 你无法继续,委托方法开火
  • 委托创建新操作B,创建操作A的副本,设置依赖关系,使A等待B完成
  • 然后代表取消原来的操作

在委托内部,您必须确保暂停队列以避免竞争条件。完成上述步骤后,您将恢复队列。在操作A中,您将有多个地方检查isCancelled,以便在取消时在main中不再执行任何工作。

答案 5 :(得分:2)

我认为您正在遵循错误的方法。如果队列中的每个操作都有优先级,并且必须按顺序执行,为什么不使用4个不同的线程?
拿一个表示状态的ivar(0:没有操作完成,1:一个操作完成,依此类推),用条件保护它:

@property(nonatomic,strong) NSCondition* condition;
@property (nonatomic) NSUInteger state; 

初始化所有内容(状态从零开始),然后创建具有不同优先级的4个不同线程。这是由线程A执行的选择器的示例:

- (void) threadA : (id) sender
{
    [condition lock];
    while(state!=3)
    {
        [condition wait];
    }
    // Do the job here
    state=4; // That's kinda useless but useful if in future you
    // want another thread that starts doing the job when A ends
    [condition unlock];
}

所以所有按照你想要的顺序执行。

编辑

你可以做我在这里做的等效,但使用NSOperationQueue:

NSOperationQueue* queue=[NSOperationQueue new];
[queue setMaxConcurrentOperationCount: 4];
[queue addOperation: [[NSInvocationOperation alloc]initWithTarget: self selector: @selector(threadA:) object: nil]]

通过说你遵循错误的方法我的意思是你不应该使用1作为maxConcurrentOperationCount的队列。主队列将此值设置为1,这就是您遇到麻烦的原因。

答案 6 :(得分:1)

正如您所发现的那样,您不能真正使用依赖项执行此操作,因为这只会影响操作的开始时间 - 如果您不知道在主要操作运行之前您需要子操作,那么不好。您无法使用单个操作队列解决此问题。

但是,由于您已经在操作队列上运行,因此无需将其他操作添加到队列中。只需就地同步执行它们。无论如何你必须等他们回来,为什么不呢?