2009-12-21 52 views
10

在我的应用程序中,我在NSOperationQueue内执行了10个异步NSURLConnections作为NSInvocationOperations。为了防止在连接之前返回每个操作有机会完成我打电话CFRunLoopRun()为在这里看到:在iPhone 3GS上消耗100%CPU的后台线程导致潜在主线程

- (void)connectInBackground:(NSURLRequest*)URLRequest { 
TTURLConnection* connection = [[TTURLConnection alloc] initWithRequest:URLRequest delegate:self]; 

// Prevent the thread from exiting while the asynchronous connection completes the work. Delegate methods will 
// continue the run loop when the connection is finished. 
CFRunLoopRun(); 

[connection release]; 
} 

一旦连接完成,最终连接代理选择调用CFRunLoopStop(CFRunLoopGetCurrent())恢复在connectInBackground()执行,允许其正常返回:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { 
    TTURLConnection* ttConnection = (TTURLConnection*)connection; 
    ... 
    // Resume execution where CFRunLoopRun() was called. 
    CFRunLoopStop(CFRunLoopGetCurrent()); 
} 

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 
    TTURLConnection* ttConnection = (TTURLConnection*)connection; 
    ... 
    // Resume execution where CFRunLoopRun() was called. 
CFRunLoopStop(CFRunLoopGetCurrent()); 
} 

这工作得很好,它是线程安全的,因为我捆绑每个连接的响应,并作为TTURLConnection子类的实例变量的数据。

NSOperationQueue声称,将其最大数量的并发操作保留为NSOperationQueueDefaultMaxConcurrentOperationCount允许它动态调整操作的数量,但在这种情况下,它总是会决定1是足够的。由于这不是我想要的,所以我已将最大数目更改为10,现在它严重拖累。

问题是这些线程(在SpringBoard和DTMobileIS的帮助下)消耗了所有可用的CPU时间,并导致主线程变得潜伏。换句话说,一旦CPU被100%利用,主线程就不会像处理UI事件那样快速地处理UI事件,从而保持流畅的UI。具体来说,表格视图滚动变得紧张。

Process Name % CPU 
SpringBoard 45.1 
MyApp   33.8 
DTMobileIS 12.2 
... 

在用户与屏幕交互或表被滚动主线程的优先级变为1.0(最大可能的)及其运行循环模式成为UIEventTrackingMode。每个操作的线程默认为0.5优先级,异步连接运行在NSDefaultRunLoopMode中。由于我对线程及其运行循环如何根据优先级和模式进行交互的理解有限,我很困惑。

有没有办法在我的应用程序的后台线程中安全地占用所有可用的CPU时间,同时仍然保证为其主线程提供尽可能多的CPU?也许是通过强制主线程按需要频繁运行? (我以为线程优先级将采取的照顾。)

更新12/23: 我终于开始变得对CPU采样器手柄和发现大部分为什么UI变得紧张不安的原因。首先,我的软件正在调用一个具有互斥信号量的库。这些锁在短时间内阻塞主线程,导致滚动略微跳过。

此外,我发现一些昂贵的NSFileManager调用和md5哈希函数,这些函数花费了太多的时间来运行。在主线程中频繁地分配大对象会导致其他一些性能命中。

我已经开始解决这些问题,并且性能已经比以前好很多。我有5个同时连接,滚动顺畅,但我仍有更多工作要做。我打算编写一本关于如何使用CPU采样器来检测和修复影响主线程性能的问题的指南。感谢迄今的评论,他们很有帮助!

UPDATE 1/14/2010: 在达到可接受的性能后,我开始意识到CFNetwork框架偶尔会泄漏内存。CFNetwork内部也随机提供异常(但很少)!我尽我所能来避免这些问题,但没有奏效。我很确定这些问题是由NSURLConnection本身的缺陷造成的。我写了测试程序,除了练习NSURLConnection之外什么也没做,他们仍然崩溃和泄漏。

最终我用ASIHTTPRequest替换了NSURLConnection,崩溃完全停止。 CFNetwork 差不多永远不会泄漏,但是,解析DNS名称时仍会出现一个非常罕见的泄漏。我现在很满意。希望这些信息能为你节省一些时间!

+0

我建议不要让线程在手机上占用100%的CPU。你会很快耗尽电池。 – 2009-12-21 17:42:56

+0

如果用户想要更多内容,他们将滚动以获取内容。 CPU仅在用户请求时才使用。可可的线程模型不应该有这样的问题。跟踪UI事件和后台线程仅为0.5时,主线程以1.0优先级运行。我想为主线程分配有保证的CPU时间。那可能吗? – 2009-12-21 19:10:59

回答

7

实际上,您不能拥有两个或三个以上的后台网络线程,并且UI保持完全响应。

优化用户响应能力,这是用户真正注意到的唯一事情。或者(我真的很讨厌这么说)在你的应用程序中添加一个“Turbo”按钮,这个按钮会启动一个非交互式模式对话框,并在并发操作数增加时将并发操作数增加到10。

+1

“在你的应用程序中添加一个”Turbo“按钮”我从未想到这一点,但这是一个甜蜜的主意!我希望尽可能快地设计应用程序,如果可能,最多可以连接10个连接,并根据需要进行退出以保持UI性能。 “Turbo”按钮对于旧设备上的用户来说是一个不错的选择,他们不介意牺牲每秒几帧来更快地获取内容。感谢您的想法! – 2009-12-21 17:47:53

3

听起来好像NSOperationQueueDefaultMaxConcurrentOperationCount设置为1的原因!我想你只是让你的手机超负荷。您可能能够解决线程优先级问题 - 我认为Mach核心是可用的,并且是正式祝福的API的一部分 - 但对我来说,这听起来像是错误的方法。

使用“系统”常量的优点之一是,苹果可以为你调整应用程序。你如何调整这个在原始iPhone上运行?对于明年的四核iPhone来说足够高吗?

+0

我实际上是为用户而不是设备优化设计。我们的大脑每两年的容量不会加倍,或者他们?)。即使机器能够同时获取1000个对象,我也不想。如果我向用户投入错误的内容,他们会变得不知所措,或者更糟糕,无聊。 – 2009-12-21 17:18:43

+1

如果你消耗的CPU太多,主(UI)线程就会饿死 - 那么你并没有为用户进行优化,而是在优化网络吞吐量。他们是两个完全不同的东西。优化用户意味着平衡可用资源,但通常意味着“保持UI的响应”。首先这是主要的规则,只要UI仍然响应,用户可以再坐几秒钟。 – 2009-12-21 17:29:23

+0

忘记回答你关于原iPhone的问题。我希望它尽可能快地消耗所有可用的CPU时间。如果一次不能跟上10个连接,那就这样吧。我可以随时优化以挤出一些额外的表现。我需要确保所有这些都是在不增加主线程延迟的情况下完成的。 – 2009-12-21 17:30:13

0

James,尽管我没有遇到过你的问题,但我成功的是在NSOperation子类中使用同步连接进行下载。

NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&requestError]; 

我用这种方法从网络位置抓取图像资产和更新目标UIImageView秒。下载发生在NSOperationQueue,更新图像视图的方法在主线程上执行。

+0

我实际上也尝试过使用同步请求,但它似乎执行相同的操作。这很有道理,因为Apple的文档说同步连接是建立在异步连接之上的。 – 2009-12-21 17:53:15