ios multithreading swift core-data nsmanagedobjectcontext

ios - Datos principales privateQueue performBlockAndWait deadlock mientras se accede a la relación



multithreading swift (2)

  1. ¿Cuáles son los mensajes "estándar"?

Cualquier mensaje enviado al contexto del objeto administrado, o cualquier objeto administrado. Tenga en cuenta que la documentación continúa aclarando ...

There are two exceptions: * Setter methods on queue-based managed object contexts are thread-safe. You can invoke these methods directly on any thread. * If your code is executing on the main thread, you can invoke methods on the main queue style contexts directly instead of using the block based API.

Por lo tanto, todo lo que no sea un método setter en un MOC se debe performBlock desde dentro de performBlock . Cualquier método en un MOC que sea de NSMainQueueConcurrencyType puede NSMainQueueConcurrencyType desde el hilo principal sin estar envuelto dentro de un performBlock .

  1. ¿Podemos establecer las propiedades de un objeto gestionado que se recupera dentro de performBlock * API de un contexto fuera de performBlock *?

No. Cualquier acceso de un objeto gestionado debe estar protegido desde dentro de performBlock en el contexto del objeto gestionado en el que reside el objeto gestionado. Tenga en cuenta la excepción para los objetos gestionados que residen en un MOC de cola principal a los que se accede desde la cola principal.

  1. ¿Por qué performBlockAndWait se comporta mal y causa bloqueo de UI en mi código de prueba?

No se está portando mal. performBlockAndWait es reentrante, pero solo cuando ya está procesando una performBlock[AndWait] .

Nunca debe usar performBlockAndWait menos que no tenga otra opción. Es especialmente problemático con contextos anidados.

Use performBlock en performBlock lugar.

Este tema ha sido discutido en muchos foros, pero todavía no puedo entender completamente cómo performBlockAndWait realmenteBlockAndWait. Según mi entendimiento, context.performBlockAndWait(block: () -> Void) ejecutará el bloque en su propia cola mientras bloquea el hilo de la persona que llama. La documentación dice que:

Agrupe mensajes "estándar" para enviar al contexto dentro de un bloque para pasar a uno de estos métodos.

¿Cuáles son los mensajes "estándar"? También dice que:

Los métodos Setter en contextos de objetos gestionados basados ​​en colas son seguros para subprocesos. Puede invocar estos métodos directamente en cualquier hilo.

¿ performBlock significa que puedo establecer propiedades de un objeto gestionado que se recupera dentro de la API performBlock * de un contexto fuera de las API performBlock *?

Según mi entendimiento, llamando a performBlockAndWait(block: () -> Void) en el contexto con tipo de concurrencia .MainQueueConcurrencyType creará un punto muerto y bloqueará la IU para siempre cuando se .MainQueueConcurrencyType desde el hilo principal. Pero en mis pruebas, no crea ningún punto muerto.

La razón por la que creo que debería crear un punto muerto es que, performBlockAndWait primero bloqueará el hilo que llama, y ​​luego ejecutará el bloque en su propio hilo. Dado que el hilo en el que el contexto debe ejecutar su bloque es el mismo que el del que llama que ya está bloqueado, por lo que nunca podrá ejecutar su bloque y el hilo permanece bloqueado para siempre.

Sin embargo, estoy enfrentando bloqueos en algún escenario extraño. Tengo el siguiente código de prueba:

@IBAction func fetchAllStudentsOfDepartment(sender: AnyObject) { let entity = NSEntityDescription.entityForName("Department", inManagedObjectContext: privateContext) let request = NSFetchRequest() request.entity = entity request.relationshipKeyPathsForPrefetching = ["students"] var department: Department? privateContext.performBlockAndWait { () -> Void in department = try! self.privateContext.executeFetchRequest(request).first as? Department print(department?.name) guard let students = department?.students?.allObjects as? [Student] else { return } for student in students { print(student.firstName) } } } @IBAction func fetchDepartment(sender: AnyObject) { let entity = NSEntityDescription.entityForName("Department", inManagedObjectContext: privateContext) let request = NSFetchRequest() request.entity = entity privateContext.performBlockAndWait { () -> Void in let department = try! self.privateContext.executeFetchRequest(request).first as? Department print(department?.name) } privateContext.performBlockAndWait { () -> Void in let department = try! self.privateContext.executeFetchRequest(request).first as? Department print(department?.name) } }

Tenga en cuenta que accidentalmente pegué performBlockAndWait dos veces en el método fetchDepartment en mi código de prueba.

  • No crea ningún punto muerto si no he llamado fetchAllStudentsOfDepartment método fetchAllStudentsOfDepartment . Pero una vez que llamo fetchAllStudentsOfDepartment , cualquier llamada al método fetchDepartment bloquea la UI para siempre.
  • Si fetchAllStudentsOfDepartment print(student.firstName) fetchAllStudentsOfDepartment print(student.firstName) en el método fetchAllStudentsOfDepartment , entonces no se bloquea. Eso significa que bloquea la interfaz de usuario solo si tengo acceso a la propiedad de una relación.
  • privateContext tiene concurrencyType establecido en .PrivateQueueConcurrencyType . El código anterior bloquea la IU solo cuando privateContext de parentContext tiene concurrencyType establecido en .MainQueueConcurrencyType .

    He probado el mismo código con otro .xcdatamodel también y ahora estoy seguro de que solo bloquea si se accede a la propiedad de una relación. Mi actual .xcdatamodel ve así:

Perdónenme si la información es extraña, pero solo estoy compartiendo todas mis observaciones después de pasar como 8 horas ya. Puedo publicar mi pila de hilos cuando la IU está bloqueada. Para resumir, tengo tres preguntas:

  1. ¿Cuáles son los mensajes "estándar"?
  2. ¿Podemos establecer las propiedades de un objeto gestionado que se recupera dentro de performBlock * API de un contexto fuera de performBlock *?
  3. ¿Por qué performBlockAndWait se comporta mal y causa bloqueo de UI en mi código de prueba?

CÓDIGO DE PRUEBA: puede descargar el código de prueba desde aquí .


  1. Los mensajes estándar son viejos jerga de Objective-C. Esto significa que debe hacer todas las llamadas a métodos regulares en ManagedObjectContext y sus ManagedObjects secundarios en performBlock o performBlockAndWait . Las únicas llamadas permitidas en un contexto privado fuera del bloque son init y setParentContext . Cualquier otra cosa debería hacerse en un bloque.

  2. No. Todos los objetos gestionados que se obtienen de un contexto privado solo deben accederse en la cola del contexto privado. Acceder (leer o escribir) desde otra cola infringe las reglas de confinamiento del hilo.

  3. La razón por la que tiene problemas de bloqueo se debe a que tiene dos niveles de contextos "mainQueue" y que está "superando" al sistema de cola. Este es el flujo:

    • Crea un contexto en la cola principal y luego lo crea como un elemento secundario de otro contexto de cola principal.
    • Crea un elemento secundario privado de ese contexto de cola principal de segundo nivel
    • Usted accede a ese contexto de cola privada de tal manera que intenta generar fallas en los objetos que actualmente ya están cargados en el contexto de la cola principal.

Debido a los dos niveles de contextos de cola principal, está causando un punto muerto donde normalmente el sistema de cola vería el punto muerto potencial y lo evitaría.

Puede probar esto cambiando su variable mainContext a:

lazy var mainContext: NSManagedObjectContext = { let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate return appDelegate!.managedObjectContext }

Y su problema desaparece porque el sistema de cola verá el bloque y lo evitará. Incluso puede ver que eso sucede colocando un punto de interrupción dentro de performBlockAndWait() y ver que todavía está en la cola principal.

Al final, no hay razón para tener dos niveles de contextos de cola principal como el de un diseño principal / secundario. En todo caso, este es un buen argumento para NO hacer eso.

Actualizar

Me perdí que había alterado el código de la plantilla en la aplicaciónDelegate y convirtió el contexto general en uno privado.

Ese patrón de tener un MOC principal por vc desecha muchos de los beneficios de Core Data. Si bien tener un privado en la parte superior y un MOC principal (que existe para toda la aplicación, no solo un VC) es un diseño válido, no funcionará si está performBlockAndWait de la cola principal de este modo.

No recomendaría utilizar performBlockAndWait desde la cola principal ya que está bloqueando toda la aplicación. performBlockAndWait solo debe utilizarse cuando se llama a la cola principal (o quizás un fondo a otro fondo).