ios - Datos principales privateQueue performBlockAndWait deadlock mientras se accede a la relación
multithreading swift (2)
- ¿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
.
- ¿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.
- ¿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étodofetchAllStudentsOfDepartment
. Pero una vez que llamofetchAllStudentsOfDepartment
, cualquier llamada al métodofetchDepartment
bloquea la UI para siempre. - Si
fetchAllStudentsOfDepartment
print(student.firstName)
fetchAllStudentsOfDepartment
print(student.firstName)
en el métodofetchAllStudentsOfDepartment
, entonces no se bloquea. Eso significa que bloquea la interfaz de usuario solo si tengo acceso a la propiedad de una relación. privateContext
tieneconcurrencyType
establecido en.PrivateQueueConcurrencyType
. El código anterior bloquea la IU solo cuandoprivateContext
deparentContext
tieneconcurrencyType
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:
- ¿Cuáles son los mensajes "estándar"?
- ¿Podemos establecer las propiedades de un objeto gestionado que se recupera dentro de
performBlock
* API de un contexto fuera deperformBlock
*? - ¿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í .
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
operformBlockAndWait
. Las únicas llamadas permitidas en un contexto privado fuera del bloque soninit
ysetParentContext
. Cualquier otra cosa debería hacerse en un bloque.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.
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).