2009-12-21 13 views
10

En mi aplicación estoy ejecutando 10 NSURLConnections asincrónicas dentro de un NSOperationQueue como NSInvocationOperations. Con el fin de evitar que cada operación de volver antes de que la conexión ha tenido la oportunidad de terminar llamo CFRunLoopRun() como se ve aquí:Subprocesos de fondo que consumen 100% de CPU en el iPhone 3GS provoca el hilo principal latente

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

Una vez finalizada la conexión, el selector último delegado respecto, pide CFRunLoopStop (CFRunLoopGetCurrent()) para reanudar la ejecución en connectInBackground(), lo que le permite volver 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()); 
} 

Esto funciona bien y es hilo de seguridad porque liado respuesta y los datos como variables de instancia en la subclase TTURLConnection de cada conexión.

NSOperationQueue afirma que dejando su número máximo de operaciones simultáneas como NSOperationQueueDefaultMaxConcurrentOperationCount le permite ajustar el número de operaciones dinámicamente, sin embargo, en este caso siempre decide que 1 es suficiente. Como eso no es lo que quiero, he cambiado el número máximo a 10 y ahora se lo dejo en serio.

El problema con esto es que estos subprocesos (con la ayuda de SpringBoard y DTMobileIS) consumen todo el tiempo de CPU disponible y hacen que el hilo principal se vuelva latente. En otras palabras, una vez que la CPU se utiliza al 100%, el hilo principal no procesa los eventos de IU tan rápido como lo necesita para mantener una interfaz de usuario uniforme. Específicamente, el desplazamiento de la vista de tabla se pone nervioso.

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

Mientras que el usuario interactúa con la pantalla o la mesa se desplaza la prioridad del hilo principal se convierte en 1,0 (la más alta posible) y su modo de bucle de ejecución se convierte en UIEventTrackingMode. Cada uno de los subprocesos de la operación tiene una prioridad de 0,5 por defecto y las conexiones asíncronas se ejecutan en el NSDefaultRunLoopMode. Debido a mi limitada comprensión de cómo los hilos y sus bucles de ejecución interactúan en función de las prioridades y los modos, estoy perplejo.

¿Hay alguna forma de consumir de forma segura todo el tiempo de CPU disponible en los hilos de fondo de mi aplicación, al mismo tiempo que garantizo que su hilo principal recibe la mayor cantidad de CPU que necesita? ¿Quizás forzando el hilo principal a ejecutarse tantas veces como sea necesario? (Pensé prioridades de los hilos se han ocupado de eso.)

ACTUALIZACIÓN 12/23: fin he empezado a conseguir una manija en el sampler de la CPU y se encontró la mayor parte de las razones por las que la interfaz de usuario se estaba convirtiendo nervioso. En primer lugar, mi software llamaba a una biblioteca que tenía semáforos de exclusión mutua. Estos bloqueos bloqueaban el hilo principal durante cortos periodos de tiempo, lo que provocaba que el desplazamiento saltara un poco.

Además, encontré algunas costosas llamadas a NSFileManager y funciones de hash md5 que tomaban demasiado tiempo para ejecutarse. La asignación de objetos grandes con demasiada frecuencia causó algunos otros golpes de rendimiento en el hilo principal.

Comencé a resolver estos problemas y el rendimiento ya es mucho mejor que antes. Tengo 5 conexiones simultáneas y el desplazamiento es suave, pero todavía tengo más trabajo por hacer. Estoy planeando escribir una guía sobre cómo usar la Muestra de CPU para detectar y solucionar problemas que afectan el rendimiento del hilo principal. Gracias por los comentarios hasta ahora, ¡fueron útiles!

ACTUALIZACIÓN 1/14/2010: Después de lograr un rendimiento aceptable comencé a darme cuenta de que el marco CFNetwork estaba perdiendo memoria ocasionalmente.Las excepciones fueron al azar (sin embargo, rara vez) se criaron dentro de CFNetwork también! Intenté todo lo posible para evitar esos problemas, pero nada funcionó. Estoy bastante seguro de que los problemas se deben a defectos dentro de NSURLConnection. Escribí programas de prueba que no hicieron nada excepto ejercitar NSURLConnection y todavía estaban cayendo y goteando.

En última instancia reemplacé NSURLConnection con ASIHTTPRequest y el bloqueo se detuvo por completo. CFNetwork casi nunca gotea, sin embargo, todavía hay una fuga muy rara que se produce al resolver un nombre DNS. Estoy bastante satisfecho ahora. ¡Espero que esta información te ahorre algo de tiempo!

+0

Yo recomendaría no tener hilos que consuman 100% de CPU de todos modos en un teléfono. Vaciará la batería rápidamente. –

+0

Si el usuario quiere más contenido, se desplaza para obtenerlo. La CPU solo se usa cuando el usuario lo solicita. El modelo de subprocesamiento de Cocoa no debería tener un problema al hacer esto. El hilo principal se ejecuta con una prioridad de 1.0 al rastrear eventos de UI y mis hilos de fondo son solo 0.5. Quiero asignar tiempo de CPU garantizado para el hilo principal. ¿Es eso posible? –

Respuesta

7

En la práctica, simplemente no puede tener más de dos o tres subprocesos de red de fondo y la interfaz de usuario permanece totalmente receptiva.

Optimice la receptividad del usuario, es lo único que el usuario realmente nota. O (y realmente odio decir esto) agregue un botón "Turbo" a su aplicación que muestre un diálogo modal no interactivo y aumente las operaciones concurrentes a 10 mientras está activo.

+1

"agrega un botón" Turbo "a tu aplicación" ¡Nunca pensé en eso, pero es una buena idea! Me gustaría diseñar la aplicación para ir lo más rápido posible, hasta 10 conexiones si es posible, retrocediendo según sea necesario para mantener el rendimiento de la IU. El botón "Turbo" sería una gran opción para los usuarios de dispositivos más antiguos que no les importa sacrificar algunos fotogramas por segundo para obtener el contenido más rápido. ¡Gracias por la idea! –

3

Parece que NSOperationQueueDefaultMaxConcurrentOperationCount está configurado en 1 por algún motivo. Creo que estás sobrecargando tu pobre teléfono. Es posible que pueda perder el tiempo con las prioridades de subprocesos, creo que el núcleo de Mach está disponible y es parte de la API oficialmente bendecida, pero a mí me parece que tiene un enfoque equivocado.

Una de las ventajas de usar constantes de "sistema" es que Apple puede ajustar la aplicación por usted. ¿Cómo vas a sintonizar esto para ejecutar en un iPhone original? ¿Es 10 lo suficientemente alto para el próximo año de cuatro núcleos de iPhone?

+0

En realidad estoy optimizando el diseño para el usuario en lugar del dispositivo. Nuestros cerebros no se duplican en capacidad cada 2 años o sí?). Incluso si la máquina es capaz de buscar 1000 objetos al mismo tiempo, no me gustaría. Si lanzo la cantidad incorrecta de contenido al usuario, se sentirán abrumados o, lo que es peor, aburridos. –

+1

Si consume tanta CPU, el hilo principal (UI) se queda sin alimentación, entonces no está optimizando para el usuario, está optimizando para el rendimiento de la red. Son dos cosas muy diferentes. Optimizar para el usuario significa equilibrar los recursos disponibles, pero a menudo significa "mantener la UI receptiva". Esa es la regla principal, sobre todo, un usuario puede sentarse durante unos segundos más, siempre y cuando la interfaz de usuario aún responda. –

+0

Se ha olvidado de responder tu pregunta sobre el iPhone original. Quiero que vaya lo más rápido posible y consumir todo el tiempo de CPU disponible. Si no puede mantenerse al día con 10 conexiones a la vez, que así sea. Siempre puedo optimizar más tarde para exprimir un rendimiento extra. Necesito asegurarme de que todo esto se haga sin agregar latencia al hilo principal. –

0

James, aunque no he experimentado su problema, lo que he tenido éxito es utilizar la conexión síncrona para descargar dentro de una subclase NSOperation.

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

que utilizan este enfoque para agarrar activos de imágenes desde ubicaciones de red y actualizar el objetivo UIImageView s. La descarga se produce en NSOperationQueue y el método que actualiza la vista de imagen se realiza en el hilo principal.

+0

En realidad, he intentado usar solicitudes sincrónicas pero parece que realiza lo mismo. Eso tiene sentido porque la documentación de Apple dice que la conexión síncrona se basa en la conexión asíncrona. –

Cuestiones relacionadas