Core Data privateQueue performBlockAndWait Deadlock beim Zugriff auf die Beziehung

8

Dieses Thema wurde in vielen Foren diskutiert, aber ich kann immer noch nicht vollständig verstehen, wie performBlockAndWait tatsächlich funktioniert. Nach meinem Verständnis führt context.performBlockAndWait(block: () -> Void) den Block in seiner eigenen Warteschlange aus, während er den Thread des Aufrufers blockiert. Dokumentation sagt Folgendes:

  

Sie gruppieren Standardnachrichten, die an den Kontext innerhalb eines Blocks gesendet werden sollen   um zu einer dieser Methoden zu gelangen.

Was sind die "Standard" -Nachrichten? Es sagt auch:

  

Setter-Methoden in kontextbasierten verwalteten Objektkontexten sind Thread-sicher.   Sie können diese Methoden direkt in einem beliebigen Thread aufrufen.

Bedeutet das, dass ich Eigenschaften eines verwalteten Objekts festlegen kann, das innerhalb der performBlock * API eines Kontexts außerhalb von performBlock * APIs abgerufen wird?

Nach meinem Verständnis wird das Aufrufen von performBlockAndWait(block: () -> Void) für den Kontext mit dem Parallelitätstyp .MainQueueConcurrencyType einen Deadlock erzeugen und die Benutzeroberfläche für immer blockieren, wenn sie vom Hauptthread aufgerufen wird. Aber in meinen Tests erzeugt es keinen Deadlock.

Der Grund, warum ich denke, dass es einen Deadlock erzeugen sollte, ist, dass performBlockAndWait zuerst den aufrufenden Thread blockiert und dann den Block auf seinem eigenen Thread ausführt. Da der Thread, in dem der Kontext seinen Block ausführen muss, derselbe wie der bereits blockierte Thread des Aufrufers ist, wird er seinen Block nie ausführen können und der Thread bleibt für immer blockiert.

Ich stehe jedoch in einem seltsamen Szenario vor Deadlocks. Ich habe unter Testcode:

%Vor%

Beachten Sie, dass ich versehentlich performBlockAndWait zweimal in fetchDepartment method in meinen Testcode eingefügt habe.

  • Es wird kein Deadlock erzeugt, wenn ich nicht angerufen habe fetchAllStudentsOfDepartment Methode. Aber sobald ich anrufe fetchAllStudentsOfDepartment , jeder Aufruf von fetchDepartment Methode blockiert die Benutzeroberfläche für immer.
  • Wenn ich print(student.firstName) in der Methode fetchAllStudentsOfDepartment lösche, wird sie nicht blockiert. Das heißt, es blockiert die Benutzeroberfläche nur, wenn ich auf die Eigenschaft einer Beziehung zugreife.
  • privateContext hat concurrencyType auf .PrivateQueueConcurrencyType gesetzt. Der obige Code blockiert die Benutzeroberfläche nur dann, wenn privateContext s parentContext concurrencyType auf .MainQueueConcurrencyType gesetzt hat.

    Ich habe den gleichen Code auch mit anderen .xcdatamodel getestet und ich bin mir sicher, dass er nur blockiert, wenn auf die Eigenschaft einer Beziehung zugegriffen wird. Mein aktuelles .xcdatamodel sieht folgendermaßen aus:

Verzeihen Sie mir, wenn die Information fremd ist, aber ich teile nur alle meine Beobachtungen, nachdem ich bereits 8 Stunden verbracht habe. Ich kann meinen Thread-Stack veröffentlichen, wenn die Benutzeroberfläche blockiert ist. Zusammenfassend habe ich drei Fragen:

  1. Was sind die "Standard" -Nachrichten?
  2. Können wir Eigenschaften eines verwalteten Objekts festlegen, das innerhalb der performBlock * API eines Kontextes außerhalb von performBlock *?
  3. abgerufen wird?
  4. Warum performBlockAndWait sich falsch verhält und in meinem Testcode einen UI-Block verursacht.

TESTCODE: Sie können den Testcode von hier .

    
Vishal Singh 02.01.2016, 07:29
quelle

2 Antworten

8
  1. Standardnachrichten sind alte Objective-C-Ausdrücke. Das bedeutet, dass Sie alle regulären Methodenaufrufe für einen ManagedObjectContext und dessen untergeordnete ManagedObjects in performBlock oder performBlockAndWait ausführen sollten. Die einzigen Aufrufe, die für einen privaten Kontext außerhalb des Blocks zulässig sind, sind init und setParentContext . Alles andere sollte in einem Block gemacht werden.

  2. Nein. Auf jedes verwaltete Objekt, das aus einem privaten Kontext abgerufen wird, darf nur in der Warteschlange dieses privaten Kontexts zugegriffen werden. Der Zugriff auf (Lesen oder Schreiben) aus einer anderen Warteschlange verstößt gegen die Thread-Einschränkungsregeln.

  3. Der Grund dafür, dass Sie Probleme beim Sperren haben, ist, dass Sie zwei Ebenen von "mainQueue" -Kontexten haben und das Warteschlangensystem "überlisten". Dies ist der Ablauf:

    • Sie erstellen einen Kontext in der Hauptwarteschlange und erstellen ihn dann als Kind eines anderen Hauptwarteschlangenkontextes.
    • Sie erstellen ein privates Kind des Hauptwarteschlangenkontexts der zweiten Ebene
    • Sie greifen auf diesen privaten Warteschlangenkontext so zu, dass er versucht, an den Objekten Fehler zu verursachen, die derzeit bereits im Hauptwarteschlangenkontext geladen sind.

Wegen der zwei Ebenen der Hauptwarteschlangenkontexte verursacht es einen Deadlock, wo normalerweise das Warteschlangensystem den potentiellen Deadlock sehen und vermeiden würde.

Sie können dies testen, indem Sie die Variable mainContext in

ändern %Vor%

Und Ihr Problem verschwindet, weil das Warteschlangensystem den Block sehen und vermeiden wird. Sie können dies sogar sehen, indem Sie innerhalb von performBlockAndWait() einen Unterbrechungspunkt setzen und sehen, dass Sie sich immer noch in der Hauptwarteschlange befinden.

Am Ende gibt es keinen Grund, zwei Ebenen von Hauptwarteschlangenkontexten wie in einem Eltern / Kind-Design zu haben. Wenn überhaupt, ist dies ein gutes Argument, das NICHT zu tun.

Aktualisieren

Ich habe übersehen, dass Sie den Vorlagencode in appDelegate geändert und den Gesamtkontext in einen privaten umgewandelt haben.

Dieses Muster, einen Haupt-MOC pro Vc zu haben, wirft viele Vorteile von Core Data weg. Während ein Privates an der Spitze und ein Haupt-MOC (das für die gesamte App existiert, nicht nur ein VC) ein gültiges Design ist, wird es nicht funktionieren, wenn Sie performBlockAndWait so aus der Hauptwarteschlange machen.

>

Ich würde nicht empfehlen, jemals performBlockAndWait aus der Hauptwarteschlange zu verwenden, da Sie die gesamte Anwendung blockieren. performBlockAndWait sollte immer nur verwendet werden, wenn TO die Hauptwarteschlange (oder vielleicht ein Hintergrund zu einem anderen Hintergrund) aufgerufen wird.

    
Marcus S. Zarra 02.01.2016, 21:08
quelle
4
  
  1. Was sind die "Standard" -Nachrichten?
  2.   

Jede Nachricht, die an den verwalteten Objektkontext oder an ein beliebiges verwaltetes Objekt gesendet wird. Beachten Sie, dass die Dokumentation weiterhin klarstellt ...

%Vor%

Somit muss alles außer einer Setter-Methode auf einem MOC von innerhalb eines performBlock aufgerufen werden. Jede Methode auf einem MOC mit dem Namen NSMainQueueConcurrencyType kann vom Hauptthread aufgerufen werden, ohne in ein performBlock eingeschlossen zu werden.

  
  1. Können wir Eigenschaften eines verwalteten Objekts festlegen, das innerhalb der performBlock * API eines Kontexts außerhalb von performBlock *?
  2. abgerufen wird?   

Nein. Jeder Zugriff auf ein verwaltetes Objekt muss im Kontext von performBlock im Kontext des verwalteten Objekts, in dem sich das verwaltete Objekt befindet, geschützt sein. Beachten Sie die Ausnahme für verwaltete Objekte, die sich in einer MOC-Hauptwarteschlange befinden, auf die von der Hauptwarteschlange zugegriffen wird.

  
  1. Warum performBlockAndWait fehlerhaft ist und einen UI-Block in meinem Testcode verursacht.
  2.   

Es ist nicht schlecht benehmen. performBlockAndWait ist reentrant, aber nur, wenn bereits ein performBlock[AndWait] -Aufruf durchgeführt wird.

Sie sollten niemals performBlockAndWait verwenden, es sei denn, Sie haben keine andere Option. Dies ist besonders bei verschachtelten Kontexten problematisch.

Verwenden Sie stattdessen performBlock .

    
Jody Hagins 02.01.2016 22:18
quelle