Wątki w tle zużywające 100% procesora w telefonie iPhone 3GS powodują ukryty wątek główny

W mojej aplikacji wykonuję 10 asynchronicznych NSURLConnections w NSOperationQueue jako NSInvocationOperations. Aby uniemożliwić każdej operacji powrót przed zakończeniem połączenia, wywołuję CFRunLoopRun (), tak jak tutaj:

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

Po zakończeniu połączenia ostateczny selektor delegowania połączenia wywołuje CFRunLoopStop (CFRunLoopGetCurrent ()), aby wznowić wykonywanie w connectInBackground (), co pozwala mu normalnie powrócić:

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

Działa to dobrze i jest bezpieczne dla wątków, ponieważ powiązałem odpowiedź każdego połączenia i dane jako zmienne instancji w podklasie TTURLConnection.

NSOperationQueue twierdzi, że pozostawienie maksymalnej liczby współbieżnych operacji jako NSOperationQueueDefaultMaxConcurrentOperationCount pozwala na dynamiczne dostosowanie liczby operacji, jednak w tym przypadku zawsze decyduje, że 1 jest wystarczająca. Ponieważ to nie jest to, czego chcę, zmieniłem maksymalną liczbę na 10 i to teraz poważnie zaciąga.

Problem polega na tym, że te wątki (za pomocą SpringBoard i DTMobileIS) zużywają cały dostępny czas procesora i powodują, że wątek główny staje się ukryty. Innymi słowy, gdy CPU jest w 100% wykorzystany, główny wątek nie przetwarza zdarzeń interfejsu użytkownika tak szybko, jak jest to konieczne, aby utrzymać płynny interfejs użytkownika. W szczególności przewijanie widoku tabeli staje się drżące.

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

Podczas gdy użytkownik wchodzi w interakcję z ekranem lub przewija tabelę, priorytetem głównego wątku staje się 1.0 (najwyższy możliwy), a tryb pętli uruchamiania staje się trybem UIEventTrackingMode. Każdy wątek operacji ma domyślnie priorytet 0,5, a połączenia asynchroniczne są uruchamiane w trybie NSDefaultRunLoopMode. Z powodu mojego ograniczonego zrozumienia, jak wątki i ich pętle uruchamiania współdziałają na podstawie priorytetów i trybów, jestem zakłopotany.

Czy istnieje sposób na bezpieczne wykorzystanie całego dostępnego czasu procesora w wątkach tła mojej aplikacji, przy jednoczesnym zagwarantowaniu, że jego główny wątek będzie miał tyle samo procesora, ile potrzebuje? Być może zmuszając główny wątek do uruchamiania tak często, jak trzeba? (Myślałem, że priorytety wątków by się tym zajęły.)

AKTUALIZACJA 12/23: W końcu zacząłem otrzymywać uchwyt na sampler CPU i znalazłem większość powodów, dla których interfejs użytkownika stawał się zdenerwowany. Po pierwsze, moje oprogramowanie wywoływało bibliotekę, która miała semafory wzajemnego wykluczania. Te blokady blokowały główny wątek przez krótki czas, powodując nieznaczne przeskakiwanie przewijania.

Ponadto znalazłem kilka drogich wywołań NSFileManager i funkcji mieszających md5, których uruchamianie zajmowało zbyt wiele czasu. Zbyt częste przydzielanie dużych obiektów powodowało inne uderzenia wydajności w głównym wątku.

Zacząłem rozwiązywać te problemy, a wydajność jest już znacznie lepsza niż wcześniej. Mam 5 jednoczesnych połączeń i przewijanie jest płynne, ale wciąż mam więcej pracy do wykonania. Mam zamiar napisać przewodnik na temat używania Samplera CPU do wykrywania i naprawiania problemów, które wpływają na wydajność głównego wątku. Dzięki za komentarze do tej pory, były pomocne!

AKTUALIZACJA 1/14/2010: Po osiągnięciu zadowalającej wydajności zacząłem zdawać sobie sprawę, że ramy CFNetwork od czasu do czasu wyciekają pamięć. Wyjątki były losowo (choć rzadko) wywoływane również w CFNetwork! Próbowałem wszystkiego, co mogłem, aby uniknąć tych problemów, ale nic nie działało. Jestem pewien, że problemy są spowodowane wadami samego NSURLConnection. Napisałem programy testowe, które nie robiły nic poza ćwiczeniem NSURLConnection i wciąż się zawieszały.

Ostatecznie zastąpiłem NSURLConnectionASIHTTPRequest i katastrofa zatrzymała się całkowicie. CFNetworkprawie nigdy nie przecieka, jednak nadal istnieje jeden bardzo rzadki wyciek, który występuje podczas rozwiązywania nazwy DNS. Jestem teraz całkiem zadowolony. Mam nadzieję, że ta informacja pozwoli Ci zaoszczędzić trochę czasu!

questionAnswers(3)

yourAnswerToTheQuestion