【问题标题】:NSOperation - Forcing an operation to wait others dynamicallyNSOperation - 强制一个操作动态等待其他操作
【发布时间】:2012-11-25 14:11:18
【问题描述】:

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

NSOperation A
NSOperation B
NSOperation C
NSOperation D
NSOperationQueue queue

我开始将A 添加到queue

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

B 取决于CC 取决于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];} 的事情。它有效,但这是正确的方法吗?也许有更好的解决方案。

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

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

感谢您的宝贵时间。

【问题讨论】:

    标签: objective-c ios nsthread nsoperation nsoperationqueue


    【解决方案1】:

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

    示例 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
    

    【讨论】:

    • 谢谢。本地 NSOperationQueue 工作并解决了我的问题。我创建了一个超类,所以我使用的每个 NSOperation 都会扩展这个类并拥有它自己的本地队列。有了这个,我可以动态添加操作并对其他线程有等待状态。我会在未来检查是否有很多队列可能是一个问题,但直到现在一切都运行正常。感谢您并感谢所有其他查看我的问题并帮助我提出建议的人。
    【解决方案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,这就是您遇到麻烦的原因。

    【讨论】:

    • 您好,感谢您的回答。所以我会用 NSThread 代替 NSOperation 吗?如果将来我需要使用类似于 NSOperationQueue 的队列,是否可以使用 NSThreads 来做到这一点?还是我必须自己实现?
    • A NSOperationQueue 只是一个为您处理操作的类。通过使用 NSThread,我的操作与您对 NSOperationQueue 的操作相同,但是手动,我将在编辑答案后向您解释。
    • 在我的情况下,我没有将 maxConcurrentOperationCount 设置为 1,该值仅限于设备支持。我遇到麻烦的原因是使用 NSOperation 我没有“等待”命令。当 4 个操作并行运行时,while(){DoNothing} 会占用太多处理时间,即使操作优先级已更改。让 NSOperation 在我的 while 循环中休眠可以解决问题。但也许有更好的方法来实现等待状态?
    • 是的,代码有效,[条件等待]它是一个可能的解决方案。但是我使用本地队列来编写更少的代码,让 ios 为我处理这些细节。无论如何,非常感谢您的时间和帮助。
    • 没问题,只是提示:如果您使用本地队列,您可以检查 maxConcurrentOperationCount 并将其设置为 5 以使此代码正常工作。
    【解决方案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];
    

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

    【讨论】:

    • 您好,感谢您的回复。使用它,我认为我需要在开始操作之前检查条件,并且我需要尝试在执行时找到解决此问题的方法。如果没有办法,我会将我的程序更改为您建议的程序。谢谢。
    【解决方案4】:

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

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

    【讨论】:

    • 您好,感谢您的回复。如果我同步执行它们,其他操作会响应取消事件吗?让我们假设 op A 依赖于 op B 并且 op B 依赖于 op C,如果我调用取消所有操作 op B 和 op C 将首先停止 isCancelled?还是只有 op A 会正确响应?
    • 您需要引用可以从某个外部源取消的操作,或者每个操作都可以检测到取消条件并停止执行。看起来您可能需要重新考虑这一切的结构。
    【解决方案5】:

    一旦 NSOperation 在它的 main 方法中,你必须通过它。没有暂停状态,只有完成或取消。

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

    所以这个过程会这样:

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

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

    【讨论】:

    • 您好,感谢您的回复。我对你的建议有一些疑问。如果我使用这种方式,当我取消原来的操作 A 时,它会在 B 完成后调用它时重新开始? B 操作会在不同的队列中运行吗?
    【解决方案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。

    【讨论】:

    • 您好,感谢您的回复。使用它,我认为我需要在开始操作之前检查条件,并且我需要尝试在执行时找到解决此问题的方法。如果没有办法,我会将我的程序更改为您建议的程序。谢谢。
    【解决方案7】:

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

    【讨论】:

    • 感谢您的回复。我已经有一个操作正在运行,如果我的 maxConcurrentOperationCount 为 1,并且我添加了一个新操作,它将执行?
    • maxConcurrentOperationCount 只是意味着它只会在任何给定时间运行一个。一个完成后,下一个将开始。
    • 所以我认为这对我的情况没有帮助。我的操作 A 正在运行,我需要运行操作 B,当 B 完成后,继续运行 A。在我的场景中,我可以运行多个操作。
    • NSOperationQueue 可能不适合你。它更多地被设计为运行一系列独立的任务。您可以在 NSURLConnection 中签出 +sendAsynchronousRequest:queue:completionHandler: 并在完成处理程序中启动下一个任务
    • 我会检查 sendAsynchronousRequest:queue:completionHandler: 看看它是否解决了我的问题。感谢您的宝贵时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-02
    • 2021-08-16
    • 2016-08-09
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    相关资源
    最近更新 更多