Threads de segundo plano que consomem 100% da CPU no iPhone 3GS causam thread principal latente

No meu aplicativo, estou executando 10 NSURLConnections assíncronos em um NSOperationQueue como NSInvocationOperations. Para evitar que cada operação retorne antes que a conexão tenha a chance de terminar, eu chamo CFRunLoopRun () como visto aqui:

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

Quando a conexão termina, o seletor delegado de conexão final chama CFRunLoopStop (CFRunLoopGetCurrent ()) para continuar a execução em connectInBackground (), permitindo que ele retorne normalmente:

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

Isso funciona bem e é thread-safe porque eu agrupei a resposta e os dados de cada conexão como variáveis ​​de instância na subclasse TTURLConnection.

NSOperationQueue afirma que deixar seu número máximo de operações simultâneas como NSOperationQueueDefaultMaxConcurrentOperationCount permite ajustar o número de operações dinamicamente, no entanto, nesse caso, ele sempre decide que 1 é suficiente. Desde que não é o que eu quero, eu mudei o número máximo para 10 e seriamente puxa agora.

O problema com isso é que esses threads (com a ajuda do SpringBoard e do DTMobileIS) consomem todo o tempo de CPU disponível e fazem com que o thread principal fique latente. Em outras palavras, uma vez que a CPU é 100% utilizada, o thread principal não está processando os eventos da interface do usuário tão rápido quanto necessário, a fim de manter uma interface do usuário suave. Especificamente, a rolagem da exibição de tabela fica nervosa.

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

Enquanto o usuário interage com a tela ou a tabela está rolando, a prioridade do thread principal se torna 1.0 (o mais alto possível) e seu modo de loop de execução se torna UIEventTrackingMode. Cada um dos encadeamentos da operação é 0.5 prioridade por padrão e as conexões assíncronas são executadas no NSDefaultRunLoopMode. Devido à minha limitada compreensão de como os threads e seus loops de execução interagem com base nas prioridades e modos, estou perplexo.

Existe uma maneira de consumir com segurança todo o tempo de CPU disponível nos threads em segundo plano do meu aplicativo, garantindo, ao mesmo tempo, que o thread principal receba o máximo de CPU necessário? Talvez forçando o thread principal para executar quantas vezes for necessário? (Eu pensei que as prioridades do thread teriam resolvido isso.)

ATUALIZAÇÃO 12/23: Eu finalmente comecei a entender o CPU Sampler e descobri a maioria das razões pelas quais a interface do usuário estava ficando nervosa. Primeiro de tudo, meu software estava chamando uma biblioteca que tinha semáforos de exclusão mútua. Esses bloqueios estavam bloqueando o thread principal por curtos períodos de tempo, fazendo com que o scroll pulasse um pouco.

Além disso, encontrei algumas chamadas caras NSFileManager e funções de hashing md5 que estavam demorando muito tempo para serem executadas. Alocar objetos grandes com muita freqüência causou alguns outros hits de desempenho no thread principal.

Eu comecei a resolver esses problemas e o desempenho já é muito melhor do que antes. Tenho 5 conexões simultâneas e a rolagem é suave, mas ainda tenho mais trabalho a fazer. Estou planejando escrever um guia sobre como usar o CPU Sampler para detectar e corrigir problemas que afetam o desempenho do thread principal. Obrigado pelos comentários até agora, eles foram úteis!

ATUALIZAÇÃO 1/14/2010: Depois de obter um desempenho aceitável, comecei a perceber que o framework CFNetwork estava vazando memória ocasionalmente. As exceções foram aleatoriamente (no entanto, raramente) sendo levantadas dentro do CFNetwork também! Eu tentei tudo que podia para evitar esses problemas, mas nada funcionou. Tenho certeza de que os problemas se devem a defeitos dentro da própria NSURLConnection. Eu escrevi programas de teste que não faziam nada além do exercício NSURLConnection e eles ainda estavam batendo e vazando.

Por fim, substituí a NSURLConnection porASIHTTPRequest e as batidas pararam completamente. CFNetworkquase nunca vazamentos, no entanto, ainda há um vazamento muito raro que ocorre ao resolver um nome DNS. Estou bastante satisfeito agora. Espero que esta informação poupe algum tempo!

questionAnswers(3)

yourAnswerToTheQuestion