Фоновые потоки, потребляющие 100% CPU на iPhone 3GS, вызывают скрытый основной поток - PullRequest
10 голосов
/ 21 декабря 2009

В моем приложении я выполняю 10 асинхронных NSURLConnections в NSOperationQueue как NSInvocationOperations. Чтобы предотвратить возврат каждой операции до того, как соединение сможет завершиться, я вызываю CFRunLoopRun (), как показано здесь:

- (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) потребляют все доступное время ЦП и приводят к тому, что основной поток становится латентным. Другими словами, как только процессор загружен на 100%, основной поток не обрабатывает события пользовательского интерфейса так быстро, как это необходимо для поддержания гладкого пользовательского интерфейса. В частности, прокрутка табличного представления становится нервной.

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

Пока пользователь взаимодействует с экраном или прокручивает таблицу, приоритет основного потока становится равным 1,0 (максимально возможный), а режим его цикла выполнения становится UIEventTrackingMode. Каждый из потоков операции имеет приоритет 0,5 по умолчанию, и асинхронные соединения выполняются в NSDefaultRunLoopMode. Из-за моего ограниченного понимания того, как потоки и их циклы выполнения взаимодействуют в зависимости от приоритетов и режимов, я в тупике.

Есть ли способ безопасно использовать все доступное время ЦП в фоновых потоках моего приложения, при этом гарантируя, что его основной поток отдает столько ЦП, сколько ему нужно? Возможно, заставляя основной поток запускаться так часто, как это необходимо? (Я думал, что приоритеты нити позаботятся об этом.)

ОБНОВЛЕНИЕ 12/23: Я наконец-то начал разбираться с процессором Sampler и нашел большинство причин, по которым интерфейс стал нервным. Во-первых, мое программное обеспечение вызывало библиотеку, в которой были семафоры взаимного исключения. Эти блокировки блокировали основной поток на короткие промежутки времени, вызывая незначительное пропускание прокрутки.

Кроме того, я нашел несколько дорогих вызовов NSFileManager и хэш-функций md5, которые выполнялись слишком долго. Слишком частое выделение больших объектов вызывало некоторые другие падения производительности в основном потоке.

Я начал решать эти проблемы, и производительность уже намного лучше, чем раньше. У меня 5 одновременных подключений, и прокрутка плавная, но у меня еще есть над чем поработать. Я планирую написать руководство по использованию CPU Sampler для выявления и устранения проблем, влияющих на производительность основного потока. Спасибо за комментарии, они были полезны!

ОБНОВЛЕНИЕ 14.01.2010: После достижения приемлемой производительности я начал понимать, что инфраструктура CFNetwork иногда теряла память. Исключения были случайным образом (но редко), возникающим и внутри CFNetwork! Я старался изо всех сил, чтобы избежать этих проблем, но ничего не получалось. Я совершенно уверен, что проблемы связаны с дефектами внутри самого NSURLConnection. Я написал тестовые программы, которые ничего не делали, кроме упражнения NSURLConnection, и они все еще не работали.

В итоге я заменил NSURLConnection на ASIHTTPRequest , и сбой полностью прекратился. CFNetwork почти никогда не протекает, однако есть еще одна очень редкая утечка, которая возникает при разрешении DNS-имени. Я вполне доволен сейчас. Надеюсь, эта информация сэкономит вам время!

Ответы [ 3 ]

7 голосов
/ 21 декабря 2009

На практике вы просто не можете иметь более двух или трех фоновых сетевых потоков, и пользовательский интерфейс остается полностью отзывчивым.

Оптимизация для отзывчивости пользователя, это единственное, что пользователь действительно замечает. Или (и мне очень неприятно это говорить) добавьте кнопку «Turbo» в ваше приложение, которая открывает неинтерактивный модальный диалог и увеличивает количество одновременных операций до 10, пока он работает.

3 голосов
/ 21 декабря 2009

Звучит так, как будто NSOperationQueueDefaultMaxConcurrentOperationCount установлено в 1 по причине! Я думаю, вы просто перегружаете свой бедный телефон. Вы можете возиться с приоритетами потоков - я думаю, что ядро ​​Маха доступно и является частью официально благословенного API - но для меня это звучит как неправильный подход.

Одним из преимуществ использования «системных» констант является то, что Apple может настроить приложение для вас. Как вы собираетесь настроить это для запуска на оригинальном iPhone? Достаточно ли 10 для четырехлетнего iPhone следующего года?

0 голосов
/ 21 декабря 2009

Джеймс, хотя я не сталкивался с вашей проблемой, я успешно использовал синхронное соединение для загрузки внутри подкласса NSOperation.

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

Я использую этот подход для извлечения ресурсов изображения из сетевых расположений и обновления цели UIImageView s. Загрузка происходит в NSOperationQueue, и метод, который обновляет представление изображения, выполняется в главном потоке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...