【问题标题】:Stopping an NSOperationQueue停止 NSOperationQueue
【发布时间】:2013-02-08 20:35:54
【问题描述】:

我有一个 NSOperationQueue,它处理循环中从 Web 服务器导入数据。它通过以下设计实现了这一点。

  1. NSURLConnect 封装在一个 NSOperation 中并添加到队列中

  2. 成功完成下载后(使用块),来自请求的数据被包装在另一个 NSOperation 中,该 NSOperation 将相关数据添加到 Core Data。此操作已添加到队列中。

  3. 成功完成后(使用另一个块),(并在指定的延迟之后)我调用启动它的方法并返回到步骤 1。因此,我在 x 秒后调用另一个服务器。

这很好用。我能够从服务器获取数据并在后台处理所有事情。而且因为这些只是 NSOperations,所以我可以将所有内容放在后台,并一次执行多个请求。这真的很好用。

我目前遇到的唯一问题是我无法成功取消操作。

我尝试过类似以下的方法:

- (void)flushQueue
{
    self.isFlushingQueue = YES;
    [self.operationQueue cancelAllOperations];
    [self.operationQueue waitUntilAllOperationsAreFinished];
    self.isFlushingQueue = NO;
    NSLog(@"successfully flushed Queue");

}

self.isFlushingQueue 是一个 BOOL,我在将任何新操作添加到队列之前使用它来检查。这似乎应该有效,但实际上并没有。关于停止我的科学怪人创作的任何想法?

编辑(已解决问题,但从不同的角度)

我仍然对我无法取消这些操作的确切原因感到困惑(我很乐意继续尝试可能的解决方案),但我对如何以稍微不同的方式解决这个问题有了一些了解。我决定只使用一个包含所有活动连接列表的数据结构(NSMutableDictionary),而不是处理取消操作并等待队列完成。像这样的:

self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:@{
                          @"UpdateContacts": @YES,
                          @"UpdateGroups" : @YES}];

然后在我将任何操作添加到队列之前,我只需询问该特定调用是打开还是关闭。我已经对此进行了测试,并且成功地对要循环的每个单独的服务器请求进行了有限控制。要关闭所有功能,我只需将所有连接设置为 @NO。

此解决方案有几个缺点(必须手动管理额外的数据结构,并且每个操作都必须重新开始以查看它在终止之前是打开还是关闭)。

编辑——追求更准确的解决方案

我删除了所有不相关的代码(注意没有错误处理)。我发布了两种方法。第一个是如何创建请求 NSOperation 的示例,第二个是生成完成块的便捷方法。

注意完成块生成器被几十个不同的请求调用,类似于第一种方法。

- (void)updateContactsWithOptions:(NSDictionary*)options
{
    //Hard coded for ease of understanding
    NSString *contactsURL = @"api/url";
    NSDictionary *params = @{@"sortBy" : @"LastName"};

    NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];

    ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:@"Contact"
                                                                 usingSelector:@selector(updateContactsWithOptions:)
                                                                   withOptions:options andParsingSelector:@selector(requestUsesRowsFromData:)];

    BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
                                                                        andDelegate:self
                                                                 andCompletionBlock:processBlock];

    //This used to check using self.isFlushingQueue
    if ([[self.activeConnections objectForKey:@"UpdateContacts"] isEqualToNumber:@YES]){
        [self.operationQueue addOperation:op];
    }

}

- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
    return ^(BOOL success, NSData *connectionData, NSError *error){

        //Pull out variables from options
        BOOL doesLoop = [[options valueForKey:@"doesLoop"] boolValue];
        NSTimeInterval timeInterval = [[options valueForKey:@"interval"] integerValue];

        //Data processed before importing to core data
        NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];

        BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport 
          andContext:self.managedObjectContext 
          andNameOfEntityToImport:entityName];

        [importOperation setCompletionBlock:^ (BOOL success, NSError *error){
             if(success){
                 NSLog(@"Import %@s was successful",entityName);
                 if(doesLoop == YES){
                     dispatch_async(dispatch_get_main_queue(), ^{
                         [self performSelector:loopSelector withObject:options afterDelay:timeInterval];
                     });
                 }
             }
         }];

        [self.operationQueue addOperation:importOperation];

    };
}

【问题讨论】:

  • 您的初始方法应该有效,但要说明为什么您的取消无效,我必须查看更多代码(您的下载 NSOperation 是如何工作的,您是如何添加其他操作的?) .如果您从多个线程访问可变字典,您的最终解决方案也可能会出现问题。
  • 我会发布一些额外的代码。我不希望不同线程上的可变字典有任何问题。我希望 NSMutableDictionary 的线程安全不会有任何问题。我可以在主线程上设置字典的值,每个连接都是在主线程上创建的,然后才被添加到队列中以在后台运行,但我承认我并不完全确定自己,因为我相对知道多线程编码。

标签: ios nsurlconnection nsoperation nsoperationqueue


【解决方案1】:

取消 NSOperation 只是一个请求,一个在NSOperation 中设置的标志。由您的 NSOperation 子类来实际执行该请求并取消它的工作。然后,您需要确保为isExecutingisFinished 等设置了正确的标志。您还需要以符合KVO 的方式执行此操作。只有设置了这些标志后,操作才完成。

文档Concurrency Programming Guide -> Configuring Operations for Concurrent Execution 中有一个示例。尽管我了解此示例可能无法正确解释所有多线程边缘情况。示例代码LinkedImageFetcher 中提供了另一个更复杂的示例:QRunLoopOperation

如果您认为您正确响应了取消请求,那么您确实需要发布您的 NSOperation 子类代码以进一步检查问题。

【讨论】:

  • 也许你可以澄清我在这个问题上的一些困惑。我的理解是,如果您明确将 NSOperation 子类声明为“并发”操作,则只需覆盖这些方法并处理标志。进行这种区分有什么好处?队列不处理将操作放在不同的线程上吗?最后,关于手头的问题。我很确定我没有实现这些功能是我的刷新方法不起作用的原因。假设,我正确地实现了这些,我原来的刷新方法是有效的和好的?
  • 我假设您正在异步使用NSURLConnection。如果是这样,这将使您的NSOperation 并发。如果您有一个固有的并发操作,但没有遵循管理它的指南,那么这可能是您问题的根源。或者,您可能正在使用sendSynchronousRequest,在这种情况下,我不知道问题的原因是什么,但我不确定这是使用 NSURLConnection 的最佳方式,尽管在您的情况下可能没问题。
【解决方案2】:

当可以添加更多操作时,不要使用自己的标志,您可以尝试

- (void)setSuspended:(BOOL)suspend

NSOperationQueue 上的方法?并且在添加新操作之前,检查队列是否被isSuspended挂起?

【讨论】:

  • 我做了这些改变。在我添加操作的任何地方,我检查是否已暂停。现在我的 flushQueue 看起来像这样: [self.operationQueue setSuspended:YES]; [self.operationQueue cancelAllOperations]; [self.operationQueue waitUntilAllOperationsAreFinished]; [self.operationQueue setSuspended:NO];但不幸的是,这具有相同的结果。
  • 好吧,这可能是一个很长的镜头。我看到你至少找到了某种解决方法 =)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多