28

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

NSOperation A
NSOperation B
NSOperation C
NSOperation D
NSOperationQueue queue

我开始添加Aqueue.

在执行期间,A我需要从中获取一些数据,直到返回我需要的数据B才能继续。AB

B对于based onC和 for Cbased on也会出现同样的情况D

为了管理这个,NSOperation我有这个代码:

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];          
}

我的问题是,当我有 3 或 4 个NSOperations等待并执行while(!operacao.isFinished && !self.isCancelled){}我的代码时,我的代码就会冻结,因为对我很重要的 NSOperation 不会被执行,即使它具有更高的优先级。

我试过的

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

  • 我可以做一些事情,而不是将操作添加到队列中[operation start]。它有效,但取消当前操作也会取消我开始的其他操作?

  • 我可以做类似的事情while(!operacao.isFinished && !self.isCancelled){[NSThread sleepForTimeInterval:0.001];}。它有效,但这是正确的方法吗?也许有更好的解决方案。

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

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

谢谢你的时间。

4

7 回答 7

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队列。这还有一个额外的好处,即您可以在取消 AlphaOperation 时轻松取消队列的操作,只需覆盖该cancel方法即可。

@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
于 2012-12-18T21:42:09.937 回答
6

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

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

1) 创建操作 A

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

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

4)将操作添加到队列中。队列将根据依赖关系执行,即。D、C、B、A。

于 2012-12-14T08:02:43.717 回答
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];

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

于 2012-12-17T01:03:59.577 回答
2

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

于 2012-12-07T11:54:19.587 回答
2

一旦 NSOperation 处于其主要方法中,您就必须完成它。没有暂停状态,只有完成或取消。

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

所以这个过程会这样:

  • 创建操作A,设置委托
  • 您无法继续,委托方法触发
  • 委托创建一个新的操作 B,创建操作 A 的副本,设置依赖关系使得 A 将等待 B 的完成
  • 然后委托取消原来的操作 A

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

于 2012-12-16T08:32:52.230 回答
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,这就是您遇到麻烦的原因。

于 2012-12-18T00:58:27.883 回答
1

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

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

于 2012-12-16T09:48:46.137 回答