【问题标题】:NSOperation and NSURLConnection mystifiedNSOperation 和 NSURLConnection 被迷惑了
【发布时间】:2014-03-07 20:21:21
【问题描述】:

我正在尝试使用 NSOperation 和 NSOperationQueue 从某个服务器下载多个图像。我的主要问题是下面的代码 sn-p 和这个链接http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/ 之间有什么区别,性能方面?我更喜欢第二种方案,因为我们对操作有更多的控制权,它更干净,如果连接失败你可以妥善处理。

如果我尝试使用以下代码从服务器下载大约 300 张图像,我的应用程序将有相当大的延迟,如果我启动应用程序,然后立即进入主屏幕,然后返回应用程序,我会崩溃,因为没有足够的时间让应用程序再次激活。如果我取消注释[queue setMaxConcurrentOperationCount:1],用户界面是响应式的,进入后台并返回前台不会崩溃。

但是如果我实现类似于上面链接的东西,我不需要担心设置maxConcurrentOperationCount,默认值就可以了。一切都是响应式的,没有崩溃,而且似乎所有队列都完成得更快。

所以这就引出了我的第二个问题,为什么 [queue setMaxConcurrentOperationCount:1] 在我下面的代码中有这么大的影响?从文档中,我认为将 maxConcurrentOperationCount 保留为默认值很好,这只是告诉队列根据某些因素决定最佳值。

这是我在 Stack Overflow 上的第一篇文章,所以希望这是有道理的,感谢您的帮助!

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//[queue setMaxConcurrentOperationCount:1];

for(NSURL *URL in URLArray) {
    [queue addOperationWithBlock:^{
        NSHTTPURLResponse *response = nil;
        NSError *error = nil;
        NSData * data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:URL] returningResponse:&response error:&error];

        if(!error && data) {
            [data writeToFile:path atomically:YES];
        }
    }];
}

【问题讨论】:

    标签: ios iphone nsurlconnection nsoperation nsoperationqueue


    【解决方案1】:

    我将以相反的顺序处理这些问题。你问:

    所以这就引出了我的第二个问题,为什么 [queue setMaxConcurrentOperationCount:1] 在我下面的代码中有这么大的影响?从文档中,我认为将 maxConcurrentOperationCount 保留为默认值很好,这只是告诉队列根据某些因素决定最佳值。

    使用NSURLConnection,您不能同时下载超过四个或五个连接。因此,如果您不设置maxConcurrentOperationCount,操作队列不知道您正在处理NSURLConnection,因此当您向队列中添加300 个NSOperation 对象时,队列将尝试启动一个非常大的他们的数量(我认为是64-ish)同时进行。但是由于只有 4 或 5 个 NSURLConnection 请求可以同时运行,其余由队列启动的请求将等待四五个可能的连接之一可用,并且下载请求如此之多,很可能有很多其中的一些会超时并失败。

    通过使用maxConcurrentOperationCount of 1,对这个问题应用了一个相当严厉的解决方案,一次只运行一个。我建议一个折衷方案,即 maxConcurrentOperationCount 为 4,它享有一定程度的并发性(以及巨大的性能提升),但不会太多以至于我们冒着连接超时和失败的风险。

    回到 Dave Drubin 的 NSOperation,他比你的 synchronousRequest 有了很大的改进。话虽如此,他却忽略了并发请求的一个相当基本的特性,即取消。您应该检查操作是否已取消,如果是,请取消连接:

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        if ([self isCancelled]) {
            [connection cancel];
            [self finish];
            return;
        }
    
        [_data appendData:data];
    }
    

    同样,他也应该在 start 方法中这样做。

    - (void)start
    {
        // The Apple docs say "Always check for cancellation before launching the task."
    
        if ([self isCancelled]) {
            [self willChangeValueForKey:@"isFinished"];
            _isFinished = YES;
            [self didChangeValueForKey:@"isFinished"];
            return;
        }
    
        if (![NSThread isMainThread])
        {
            [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
            return;
        }
    
        NSLog(@"opeartion for <%@> started.", _url);
    
        [self willChangeValueForKey:@"isExecuting"];
        _isExecuting = YES;
        [self didChangeValueForKey:@"isExecuting"];
    
        NSURLRequest * request = [NSURLRequest requestWithURL:_url];
        _connection = [[NSURLConnection alloc] initWithRequest:request
                                                      delegate:self];
        if (_connection == nil)
            [self finish];
    }
    

    我可能会建议对 Dave 的示例进行其他风格上的改进,但这都是小事,我认为他掌握了大部分大局的东西。未能检查是否取消是唯一让我头疼的明显大问题。

    无论如何,有关并发操作的讨论,请参阅Concurrency Programming Guide为并发执行配置操作部分。

    此外,在测试此类大量下载时,我建议您使用 Network Link Conditioner 对您的应用程序进行压力测试(可用于 Mac/模拟器,可在“Xcode”的“硬件 IO 工具”下下载 - “打开开发者工具”-“更多开发者工具”;如果您启用您的iOS设备进行开发,在“设置”应用的“常规”-“开发者”下还有一个网络链接调节器设置)。当我们在高度优化的开发环境场景中测试我们的应用程序时,许多这些与超时相关的问题并没有表现出来。使用网络链路调节器模拟不太理想的真实场景非常重要。

    【讨论】:

    • 小幅补充这个原本很好的答案:NSOperationQueue 中的默认并发操作数可以通过NSOperationQueueDefaultMaxConcurrentOperationCount 获得,AFAIK 它等于 CPU 的数量,但是一个实现细节。这可能适用于 CPU 绑定操作,但绝不是网络绑定操作的最佳选择。也就是说,您希望将其设置为显式,其中 2 到 4 是一个很好的权衡。
    【解决方案2】:

    我更喜欢第二种解决方案,因为我们对操作有更多的控制权

    如果您参考自己的解决方案,那么实际上情况恰恰相反:

    由于同步便捷方法sendSynchronousRequest:,您基本上无法完成更实际的要求,例如身份验证、更好的错误处理和自定义数据处理,以及任何非演示或应用程序通常需要的许多其他功能玩具应用。

    但遗憾的是缺乏取消操作的能力。阻止操作一旦开始就无法取消。而且也不清楚你想如何取消“for循环”。因此,一旦循环开始,您将无法停止。

    您可能希望在网络和 SO 上搜索更复杂(和更现代)的方法。

    【讨论】:

    • +1 对sendSynchronousRequest 的观察结果。关于“缺乏取消操作的能力”,Dave Drubin 的操作类可以很容易地扩展以正确处理取消。我不确定你是否想仅仅因为一个失败而取消它们(但如果你愿意,你可以很容易地做到这一点;取决于 OP 想要什么)。或者,我想我听说过一些“有前途的”解决方案。 :) 无论如何,在我看来,关键意见是(a)使其可以取消; (b) 使其同时进行;但是 (c) 一次将并发限制为 4 或 5 个操作。
    【解决方案3】:

    当队列的MaxConcurrentOperationCount > 1 时,队列很有可能会被操作完成而锁定和解锁,而您仍在向队列中添加更多作业,但是当您将其设置为 1 时,队列会在工作开始工作之前更完整,这意味着 URLArray 的循环更快地完成(避免不断“死锁”)。

    如果我没记错 NSOperationQueue api,您可以“暂停”和“恢复”它...我建议您将其设置为“暂停”,直到您完成添加所有作业,然后将其设置为恢复

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-19
      • 1970-01-01
      • 2018-07-30
      • 1970-01-01
      相关资源
      最近更新 更多