2009-02-16 25 views
15

Mi cliente de iPhone tiene una gran cantidad de solicitudes asincrónicas, la mayoría de las veces modifica constantemente las colecciones estáticas de diccionarios o matrices. Como resultado, es común para mí ver las estructuras de datos más grandes que tardan más en recuperar de un servidor con los siguientes errores:Uso de iPhone de mutexes con solicitudes de URL asíncronas

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.' 

lo general, esto significa que dos peticiones al servidor volver con los datos que están tratando de modificar la misma colección Lo que estoy buscando es un tutorial/ejemplo/comprensión de cómo estructurar correctamente mi código para evitar este error perjudicial. Creo que la respuesta correcta son mutexes, pero nunca los he usado personalmente.

Esto es el resultado de realizar solicitudes HTTP asincrónicas con NSURLConnection y luego usar NSNotification Center como medio de delegación una vez que se completan las solicitudes. Al disparar solicitudes que cambian los mismos conjuntos de recopilación, obtenemos estas colisiones.

Respuesta

15

Si es posible acceder a cualquier información (incluidas las clases) desde dos subprocesos simultáneamente, debe seguir los pasos para mantenerlos sincronizados.

Afortunadamente Objective-C lo hace ridículamente fácil con la palabra clave sincronizada. Estas palabras clave toman como argumento cualquier objeto Objective-C. Cualquier otro subproceso que especifique el mismo objeto en una sección sincronizada se detendrá hasta que termine el primero.

-(void) doSomethingWith:(NSArray*)someArray 
{  
    // the synchronized keyword prevents two threads ever using the same variable 
    @synchronized(someArray) 
    { 
     // modify array 
    } 
} 

Si usted necesita para proteger algo más que una variable se debe considerar el uso de un semáforo que representa el acceso a ese conjunto de datos.

// Get the semaphore. 
id groupSemaphore = [Group semaphore]; 

@synchronized(groupSemaphore) 
{ 
    // Critical group code. 
} 
28

Existen varias formas de hacerlo. Lo más simple en su caso probablemente sea usar la directiva @synchronized. Esto le permitirá crear un mutex sobre la marcha utilizando un objeto arbitrario como el bloqueo.

@synchronized(sStaticData) { 
    // Do something with sStaticData 
} 

Otra forma sería utilizar la clase NSLock. Cree el bloqueo que desea usar, y luego tendrá un poco más de flexibilidad cuando se trata de adquirir el mutex (con respecto al bloqueo si el bloqueo no está disponible, etc.).

NSLock *lock = [[NSLock alloc] init]; 
// ... later ... 
[lock lock]; 
// Do something with shared data 
[lock unlock]; 
// Much later 
[lock release], lock = nil; 

Si usted decide tomar cualquiera de estos enfoques será necesario adquirir el bloqueo de lecturas y escrituras dado que está utilizando NSMutableArray/Set/lo que sea como un almacén de datos. Como ha visto, NSFastEnumeration prohíbe la mutación del objeto enumerado.

Pero creo que otro problema es la elección de las estructuras de datos en un entorno de subprocesos múltiples. ¿Es estrictamente necesario acceder a tus diccionarios/matrices desde múltiples hilos? ¿O podrían los hilos de fondo combinar los datos que reciben y luego pasarlos al hilo principal, que sería el único hilo permitido para acceder a los datos?

+0

El problema es que los hilos 'de fondo' no los he creado explícitamente. Son el resultado de solicitudes de NSURLConnection asincrónicas. No tengo forma de hablar con el hilo principal a través del código. Sus otras sugerencias son útiles y lo agradezco. – Coocoo4Cocoa

+0

Creo que se llamará al delegado de NSURLConnection en el hilo que inició la operación de carga, no necesariamente en el hilo que creó el objeto. Para que pueda unir los datos en sus métodos de delegado. – sbooth

+0

¡Sí! gracias, funciona para mi – Armanoide

0

En respuesta a la respuesta sStaticData y NSLock (comentarios están limitados a 600 caracteres), no tiene que ser muy cuidadoso con la creación de la sStaticData y los objetos NSLock de una manera segura hilo (para evitar la escenario muy poco probable de múltiples bloqueos creados por diferentes hilos)?

creo que hay dos soluciones:

1) Puede mandato de esos objetos se crean al principio del día en el único hilo de la raíz.

2) Defina un objeto estático que se crea automáticamente al inicio del día para usarlo como bloqueo, p. un NSString estática se puede crear en línea:

static NSString *sMyLock1 = @"Lock1"; 

Entonces creo que se puede utilizar con seguridad

@synchronized(sMyLock1) 
{ 
    // Stuff 
} 

lo demás, creo que siempre va a terminar en un 'pollo y el huevo con la creación de las cerraduras en un hilo de manera segura?

Por supuesto, es muy poco probable que aciertes a ninguno de estos problemas ya que la mayoría de las aplicaciones de iPhone se ejecutan en un solo hilo.

No conozco la sugerencia de [Semaforo de grupo] anteriormente, eso también podría ser una solución.

+0

"La respuesta anterior"? Hay otras dos respuestas ahora mismo. –

+0

Lo he actualizado para aclarar (FTR, me refiero a la respuesta anterior, dado que menciono sStaticData y NSLock no hay mucha ambigüedad). –

0

N.B. Si se usa la sincronización no se olvide de añadir -fobjc-exceptions a sus banderas: CCG “Control de excepciones”

de Objective-C proporciona soporte para la sincronización hilo y manejo de excepciones , que se explican en este artículo y Para activar el soporte para estas características, use el interruptor -fobjc-excepciones de la Colección del compilador GNU (GCC) versión 3.3 y posterior.

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html

0

utilizar una copia del objeto para modificarlo. Como intenta modificar la referencia de una matriz (colección), mientras que otra persona también puede modificarla (acceso múltiple), crear una copia le servirá. Crea una copia y luego enumera sobre esa copia.

NSMutableArray *originalArray = @[@"A", @"B", @"C"]; 
NSMutableArray *arrayToEnumerate = [originalArray copy]; 

Ahora modifique el arrayToEnumerate. Como no se hace referencia a originalArray, sino que es una copia del original Array, no causará problemas.

0

Hay otras maneras si no desea la sobrecarga de bloqueo ya que tiene su costo. En lugar de usar un bloqueo para proteger el recurso compartido (en su caso, podría ser un diccionario o una matriz), puede crear una cola para serializar la tarea que está accediendo a su código de sección crítica. Queue no toma la misma cantidad de penalización que los bloqueos, ya que no requiere atrapar en el kernel para adquirir mutex. simplemente poner

dispatch_async(serial_queue, ^{ 
    <#critical code#> 
}) 

En caso si usted quiere ejecución actual a esperar hasta que la tarea completa, se puede utilizar

dispatch_sync(serial_queue Or concurrent, ^{ 
    <#critical code#> 
}) 

En general, si la ejecución doest necesidad de no esperar, asíncrono es una forma preferida de hacer.

Cuestiones relacionadas