Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Background threads consuming 100% CPU on iPhone 3GS causes latent main thread

In my application I am executing 10 asynchronous NSURLConnections within an NSOperationQueue as NSInvocationOperations. In order to prevent each operation from returning before the connection has had a chance to finish I call CFRunLoopRun() as seen here:

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

Once the connection finishes, the final connection delegate selector calls CFRunLoopStop(CFRunLoopGetCurrent()) to resume the execution in connectInBackground(), allowing it to return normally:

- (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());
}

This works well and it is thread safe because I bundled each connection's response and data as instance variables in the TTURLConnection subclass.

NSOperationQueue claims that leaving its maximum number of concurrent operations as NSOperationQueueDefaultMaxConcurrentOperationCount allows it to adjust the number of operations dynamically, however, in this case it always decides that 1 is enough. Since that is not what I want, I have changed the maximum number to 10 and it seriously hauls now.

The problem with this is that these threads (with the help of SpringBoard and DTMobileIS) consume all of the available CPU time and cause the main thread to become latent. In other words, once the CPU is 100% utilized, the main thread is not processing UI events as fast as it needs to in order to maintain a smooth UI. Specifically, table view scrolling becomes jittery.

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

While the user interacts with the screen or the table is scrolling the main thread's priority becomes 1.0 (the highest possible) and its run loop mode becomes UIEventTrackingMode. Each of the operation's threads are 0.5 priority by default and the asynchronous connections run in the NSDefaultRunLoopMode. Due to my limited understanding of how threads and their run loops interact based on priorities and modes, I am stumped.

Is there a way to safely consume all available CPU time in my app's background threads while still guaranteeing that its main thread is given as much of the CPU as it needs? Perhaps by forcing the main thread to run as often as it needs to? (I thought thread priorities would have taken care of that.)

UPDATE 12/23: I have finally started getting a handle on the CPU Sampler and found most of the reasons why the UI was becoming jittery. First of all, my software was calling a library which had mutual exclusion semaphores. These locks were blocking the main thread for short periods of time causing the scroll to skip slightly.

In addition, I found some expensive NSFileManager calls and md5 hashing functions which were taking too much time to run. Allocating big objects too frequently caused some other performance hits in the main thread.

I have begun to resolve these issues and the performance is already much better than before. I have 5 simultaneous connections and the scrolling is smooth, but I still have more work to do. I am planning to write a guide on how to use the CPU Sampler to detect and fix issues that affect the main thread's performance. Thanks for the comments so far, they were helpful!

UPDATE 1/14/2010: After achieving acceptable performance I began to realize that the CFNetwork framework was leaking memory occasionally. Exceptions were randomly (however, rarely) being raised inside CFNetwork too! I tried everything I could to avoid those problems but nothing worked. I am quite sure that the issues are due to defects within NSURLConnection itself. I wrote test programs which did nothing except exercise NSURLConnection and they were still crashing and leaking.

Ultimately I replaced NSURLConnection with ASIHTTPRequest and the crashing stopped entirely. CFNetwork almost never leaks, however, there is still one very rare leak which occurs when resolving a DNS name. I am quite satisfied now. Hopefully this information saves you some time!

like image 867
James Wald Avatar asked Dec 21 '09 15:12

James Wald


People also ask

What is main thread and background thread?

For example, if your app makes a network request from the main thread, your app's UI is frozen until it receives the network response. You can create additional background threads to handle long-running operations while the main thread continues to handle UI updates.

Is the main thread faster?

The main thread is starting first spinning out a thread which needs to spin up then moving on to its print statement. While the second thread is spinning up the first is completing. It's faster because it started first.


2 Answers

In practice you simply cannot have more than two or three background network threads and have the UI stay fully responsive.

Optimize for user responsiveness, it's the only thing a user really notices. Or (and I really hate to say this) add a "Turbo" button to your app that puts up a non-interactive modal dialog and increases concurrent operations to 10 while it is up.

like image 115
Kendall Helmstetter Gelner Avatar answered Sep 23 '22 20:09

Kendall Helmstetter Gelner


It sounds as though NSOperationQueueDefaultMaxConcurrentOperationCount is set to 1 for a reason! I think you're just overloading your poor phone. You may be able to mess around with threading priorities -- I think the Mach core is available and part of the officially blessed API -- but to me it sounds like the wrong approach.

One of the advantages of using "system" constants is that Apple can tune the app for you. How are you going to tune this to run on an original iPhone? Is 10 high enough for next years quad-core iPhone?

like image 36
Stephen Darlington Avatar answered Sep 21 '22 20:09

Stephen Darlington