61 votes

Que veut dire Apple lorsqu'elle affirme qu'un NSManagedObjectContext appartient au thread ou à la file d'attente qui l'a créé ?

Il semble qu'en novembre, Apple ait mis à jour à la fois l'application Référence de classe NSManagedObjectContext et le Guide de programmation des données de base afin de bénir explicitement les GCD Dispatch Queues et les NSOperationQueues en série en tant que mécanismes acceptables pour synchroniser l'accès à un système de gestion de l'information. NSManagedObjectContext . Mais leurs conseils semblent ambigus et peut-être contradictoires, et je veux m'assurer que je les ai bien compris.

Auparavant, il semblait admis qu'une NSManagedObjectContext ne pouvait être consulté qu'à partir du thread qui l'avait créé, et que l'utilisation d'une file d'attente série pour la synchronisation n'était pas suffisante ; bien que les files d'attente série n'effectuent qu'une seule opération à la fois, ces opérations peuvent potentiellement être planifiées sur différents threads, et un MOC n'aime pas cela.

Mais maintenant, d'après le guide de programmation, nous avons :

Vous pouvez utiliser des threads, des files d'attente d'opérations en série ou des files d'attente de distribution pour la concurrence. Dans un souci de concision, cet article utilise le terme "thread" pour désigner l'un de ces éléments.

Jusqu'ici, tout va bien (bien que la confusion entre les fils et les files d'attente ne soit pas utile). Je peux donc utiliser sans risque un seul contexte par file d'attente (série), au lieu d'un par opération/bloc, n'est-ce pas ? Apple a même une représentation visuelle de ceci dans les sessions WWDC de Core Data.

Mais... où crée-t-on le contexte de la file d'attente ? Dans le NSManagedObjectContext la documentation, l'état d'Apple :

[Un contexte] suppose que le propriétaire par défaut est le thread ou la file d'attente qui l'a alloué - ceci est déterminé par le thread qui appelle sa méthode init. Vous ne devez donc pas initialiser un contexte sur un thread puis le passer à un autre thread.

Donc maintenant nous avons l'idée d'un NSManagedObjectContext ayant besoin de savoir qui est son propriétaire. Je suppose que cela signifie que la première opération à être exécutée dans la file d'attente doit créer le MOC et sauvegarder une référence à celui-ci pour que les autres opérations puissent l'utiliser.

C'est bien ça ? La seule raison pour laquelle j'hésite, c'est que les NSManagedObjectContext L'article poursuit :

Au lieu de cela, vous devez transmettre une référence à un coordinateur de magasin persistant et faire en sorte que le thread/la file d'attente récepteur crée un nouveau contexte dérivé de celui-ci. Si vous utilisez NSOperation, vous devez créer le contexte dans main (pour une file en série) ou start (pour une file concurrente).

Apple semble maintenant confondre les opérations avec les files d'attente qui programment leur exécution. Cela me fait perdre la tête et me fait me demander s'ils veulent vraiment que vous créiez un nouveau MOC pour chaque opération après tout. Qu'est-ce que j'ai raté ?

64voto

Ben Points 746

Le NSManagedObjectContext et tous les objets gérés qui lui sont associés doivent être rattachés à un seul acteur (thread, file d'attente sérialisée, NSOperationQueue avec une concurrence maximale de 1).

Ce schéma est appelé confinement ou isolement des fils. Il n'y a pas d'expression géniale pour (thread || file d'attente sérialisée || NSOperationQueueue avec une concurrence maximale = 1), c'est pourquoi la documentation poursuit en disant "nous utiliserons simplement 'thread' pour le reste de la documentation sur les données de base lorsque nous ferons référence à l'une de ces trois façons d'obtenir un flux de contrôle sérialisé".

Si vous créez un MOC sur un thread, puis l'utilisez sur un autre, vous avez violé le confinement des threads en exposant la référence de l'objet MOC à deux threads. C'est simple. Ne le faites pas. Ne croisez pas les flux.

Nous mentionnons explicitement la NSOperation parce que, contrairement aux threads et au GCD, elle présente un problème étrange : -init s'exécute sur le thread qui crée la NSOperation, mais -main s'exécute sur le thread qui exécute la NSOperation. C'est logique si vous y regardez de plus près, mais ce n'est pas intuitif. Si vous créez votre MOC dans -[NSOperation init], alors NSOperation violera utilement le confinement des threads avant même que votre méthode -main ne s'exécute et vous êtes fichu.

Nous décourageons activement / déprécions l'utilisation des MOC et des fils de discussion de toute autre manière. Bien qu'il soit théoriquement possible de faire ce que bbum mentionne, personne n'a jamais réussi à le faire correctement. Tout le monde a trébuché, a oublié un appel nécessaire à -lock à un endroit, "init fonctionne où ?", ou s'est surpassé lui-même. Avec les pools d'autorelease, la boucle d'événements de l'application, le gestionnaire d'annulation, les liaisons cacao et le KVO, il y a tellement de façons pour un thread de conserver une référence à un MOC après que vous ayez essayé de le passer ailleurs. C'est beaucoup plus difficile que ce que même les développeurs Cocoa avancés imaginent jusqu'à ce qu'ils commencent à déboguer. Ce n'est donc pas une API très utile.

La documentation a été modifiée afin de clarifier et de souligner que le modèle de confinement des fils est la seule façon saine de procéder. Vous devriez considérer que l'utilisation de -lock et -unlock sur NSManagedObjectContext est (a) impossible et (b) de facto dépréciée. Il n'est pas littéralement déprécié parce que le code fonctionne aussi bien qu'il l'a toujours fait. Mais votre code qui l'utilise est erroné.

Certaines personnes ont créé des MOCs sur un fil, et les ont transmis à un autre sans appeler -lock. Cela n'a jamais été légal. Le fil qui a créé le MOC a toujours été le propriétaire par défaut du MOC. Ce problème est devenu plus fréquent pour les MOC créés sur le fil principal. Les MOC du thread principal interagissent avec la boucle d'événements principale de l'application pour l'annulation, la gestion de la mémoire et d'autres raisons. Sous 10.6 et iOS 3, les MOC tirent un avantage plus agressif du fait qu'ils appartiennent au thread principal.

Bien que les files d'attente ne soient pas liées à des fils spécifiques, si vous créez un MOC dans le contexte d'une file d'attente, les bonnes choses se produiront. Votre obligation est de suivre l'API publique.

Si la file d'attente est sérialisée, vous pouvez partager le MOC avec les blocs successifs qui s'exécutent sur cette file.

Il ne faut donc en aucun cas exposer un NSManagedObjectContext* à plus d'un thread (acteur, etc.). Il y a une ambiguïté. Vous pouvez passer la NSNotification* de la notification didSave à la méthode -mergeChangesFromContextDidSaveNotification : d'un autre thread.

  • Ben

11voto

Kevin Ballard Points 88866

On dirait que tu avais raison. Si vous utilisez des threads, le thread qui veut le contexte doit le créer. Si vous utilisez des files d'attente, la file d'attente qui veut le contexte doit le créer, très probablement en tant que premier bloc à exécuter sur la file d'attente. On dirait que la seule partie qui prête à confusion est celle qui concerne les NSOperations. Je pense que la confusion vient du fait que les NSOperations ne fournissent aucune garantie quant au thread/queue sous-jacent sur lequel elles s'exécutent, donc il peut ne pas être sûr de partager un MOC entre les opérations même si elles s'exécutent toutes sur la même NSOperationQueue. Une autre explication est qu'il s'agit simplement d'une documentation confuse.

Pour résumer :

  • Si vous utilisez des fils de discussion, créez le MOC sur le fil de discussion qui le souhaite.
  • Si vous utilisez GCD, créez le MOC dans le tout premier bloc exécuté sur votre file d'attente série.
  • Si vous utilisez la NSOperation, créez le MOC à l'intérieur de la NSOperation et ne le partagez pas entre les opérations. C'est peut-être un peu paranoïaque, mais NSOperation ne garantit pas sur quel thread/queue sous-jacent il s'exécute.

Editar : Selon bbum, la seule véritable exigence est que l'accès doit être sérialisé. Cela signifie que vous pouvez partager une MOC entre plusieurs NSOpérations tant que les opérations sont toutes ajoutées à la même file d'attente, et que la file d'attente n'autorise pas les opérations simultanées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X