2014-02-26 4 views
10

Ich habe Probleme mit Core Data, die ich nicht lösen kann. Ich habe auf dem harten Weg über Nebenläufigkeitsprobleme in Core Data erfahren, daher bin ich sehr vorsichtig und führe nur irgendwelche Kerndatenoperationen in performBlock: und performBlockAndWait: Blöcken durch.Core Data Deadlocking beim Ausführen von Abrufanforderungen innerhalb von performBlockAndWait Blöcke

geht hier meinen Code:

/// Executes a fetch request with given parameters in context's block. 
+ (NSArray *)executeFetchRequestWithEntityName:(NSString *)entityName 
           predicate:(NSPredicate *)predicate 
           fetchLimit:(NSUInteger)fetchLimit 
          sortDescriptor:(NSSortDescriptor *)sortDescriptor 
           inContext:(NSManagedObjectContext *)context{ 
    NSCAssert(entityName.length > 0, 
      @"entityName parameter in executeFetchRequestWithEntityName:predicate:fetchLimit:sortDescriptor:inContext:\ 
      is invalid"); 

    __block NSArray * results = nil; 

    NSPredicate * newPredicate = [CWFCoreDataUtilities currentUserPredicateInContext:context]; 
    if (predicate){ 
     newPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[newPredicate, predicate]]; 
    } 

    [context performBlockAndWait:^{ 

     NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:entityName]; 
     request.fetchLimit = fetchLimit; 
     request.predicate = newPredicate; 
     if (sortDescriptor) { 
      request.sortDescriptors = @[sortDescriptor]; 
     } 

     NSError * error = nil; 
     results = [context executeFetchRequest:request error:&error]; 

     if (error){ 
      @throw [NSException exceptionWithName:NSInternalInconsistencyException 
              reason:@"Fetch requests are required to succeed."  
             userInfo:@{@"error":error}]; 
      NSLog(@"ERROR! %@", error); 
     } 

     NSCAssert(results != nil, @"Fetch requests must succeed"); 
    }]; 

    return results; 
} 

Wenn ich diese Methode gleichzeitig von zwei verschiedenen Threads eingeben und zwei unterschiedliche Kontexte passieren, ich in einer Sackgasse auf dieser Reihe führen: results = [context executeFetchRequest:request error:&error];

was interessant ist: Es scheint, als könnten beide Threads keine Sperre für den Persistent Store Coordinator erhalten, um eine Abrufanforderung auszuführen.

Alle meine Kontexte sind NSPrivateQueueConcurrencyType.

Ich kann nicht sagen, warum schließe ich die App und was soll ich anders machen. Meine Nachforschungen zu Stack Overflow gaben mir nichts, da die meisten Leute alle Sperren durch das Absetzen der Abrufanforderungen in der MOC-Warteschlange behoben haben, was ich bereits getan habe.

Ich werde alle Informationen zu diesem Thema zu schätzen wissen. Fühlen Sie sich frei, Dokumentationslinks und andere lange Lesevorgänge zu bieten: Ich bin begierig, mehr über alle Arten von Nebenläufigkeitsproblemen und -strategien zu lernen.

+0

Könnten Sie Code vorsehen: 'currentUserPredicateInContext:' und 'predicate' Konstruktion (verwenden sie Objekt aus anderen Kontexten konstruiert werden)? –

+1

Sind Sie sicher, dass es ein Deadlock ist? Ich schlage vor, Sie die Stack-Spuren der Threads Post –

+0

@DanShelly Der Bug ist sehr schwer zu reproduzieren, ich habe es heute nicht gefangen, also keine Stack-Spuren. Prädikate haben das Format "owner =% @", wobei% @ eine Zeichenfolge aus Benutzerstandardwerten ist. Ich habe überhaupt keine MObjects in Prädikaten verwendet. Ich bin sicher, es ist ein Deadlock, da in den Stack-Traces zwei meiner Threads auf mutex_wait hängen bleiben, was aus einem Aufruf von executeFetchRequest resultiert. –

Antwort

0

Wenn Sie mehr über Core Data (und Threading) erfahren möchten, wird die folgende Website sehr hilfreich sein. Ich besuchte Matthew Morey Vortrag auf dem Atlanta CocoaConf 2013

High Performance Core Data (http://highperformancecoredata.com)

Der Beispielcode Begleiter auf der Website unter: https://github.com/mmorey/MDMHPCoreData

Alles was Sie brauchen in Ihrer App zu tun , soll eine Singleton-Instanz (irgendwo) der MDMPersistenceStack-Klasse haben.

Soweit Ihr Problem/Problem, obwohl die Apple-Dokumentation für NSManagedObjectContext-Klasse, ermöglicht Code für die Art geschrieben werden, die Sie haben (mit den Core Data-Operationen für eine NSManagedObjectContext-Instanz in einem Block durchgeführt) und zu in gewissem Maße ermutigt das - ich möchte darauf hinweisen, dass das nicht der einzige Weg ist.

Immer, wenn ich Apps repariert habe, die Core Data Concurrency Probleme haben (locking), ist die einfachste Sache meines Erachtens die Erstellung eines privaten NSManagedObjectContext innerhalb des Threads oder Blocks, für den ich Core Data Operationen ausführen möchte.

Es mag nicht der eleganteste Ansatz sein, aber es hat mich nie im Stich gelassen. Es ist immer garantiert, dass der NSManagedObjectContext im selben Thread erstellt und ausgeführt wird, da der NSManagedObjectContext explizit erstellt wird.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

dispatch_async(queue, ^{ 
     /* 
     Create NSManagedObjectContext 
     Concurrency of Managed Object Context should be set to NSPrivateQueueConcurrencyType 
     If you use the MDMPersistenceStack class this is handled for you. 
     */ 
     NSManagedObjectContext *managedObjectContext = .... 

     /* 
     Call the method that you have listed in your code. 
     Let's assume that this class method is in MyClass 
     Remove the block that you have in your method, as it's not needed 
     */ 
     [MyClass executeFetchRequestWithEntityName: ......] // rest of parameters 
}); 
+0

Das ist eine sehr schlechte Antwort. Zuallererst, wie in der Klassenreferenz von [NSManagedObjectContext:] (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/NSManagedObjectContext.html) Private Queue (NSPrivateQueueConcurrencyType) - Der Kontext erstellt und verwaltet eine private Warteschlange. (...) Sie verwenden Kontexte, die die Queue-basierten Gleichzeitigkeitstypen in Verbindung mit performBlock: und performBlockAndWait: verwenden. Sie gruppieren "Standard" -Nachrichten, die an den Kontext innerhalb eines Blocks gesendet werden, um an eine dieser Methoden zu übergeben. –

+0

Also, was ich mit diesen Blöcken mache, ist der richtige Weg, die Parallelität mit Kontexten von NSPrivateQueueConcurrencyType zu behandeln, und was Sie vorschlagen, ist wahrscheinlich die Art, wie Sie es mit NSConfinementConcurrencyType-Kontexten tun. –

+0

Wir müssen zustimmen, nicht zuzustimmen, dass meine Antwort eine schlechte Antwort ist. :-) Vielleicht magst du den Teil nicht - wo ich versucht habe, Dinge zu vereinfachen. In einer Welt vor iOS 4 wäre dies eine hervorragende Antwort gewesen. – kurtn718

2

was interessant ist: wie es scheint, beide Threads eine Sperre auf dem persistenten Speicher Coordinator, um eine Abrufanforderung auszuführen nicht erwerben können.

Der beständige Speicherkoordinator ist eine serielle Warteschlange. Wenn ein Kontext darauf zugreift, wird ein anderer Kontext blockiert.

Von Apple Docs:

Koordinatoren tun das Gegenteil für die Bereitstellung von Concurrency-sie serialisiert Operationen. Wenn Sie mehrere Threads für verschiedene Schreiboperationen verwenden möchten, verwenden Sie mehrere Koordinatoren. Beachten Sie, dass wenn mehrere Threads direkt mit einem Koordinator arbeiten, diese explizit gesperrt und entsperrt werden müssen.

Wenn Sie mehrere Hintergrundabrufanforderungen gleichzeitig ausführen müssen, benötigen Sie mehrere persistente Speicherkoordinatoren.

Mehrere Koordinatoren machen Ihren Code nur geringfügig komplexer, sollten aber nach Möglichkeit vermieden werden. Müssen Sie mehrere Abholungen gleichzeitig durchführen? Könnten Sie einen größeren Abruf durchführen und dann die speicherinternen Ergebnisse filtern?

-1

Ich habe es so gemacht. Es hat das Problem für mich behoben. Ich habe auch viele Deadlocks erlebt. Sehen Sie, ob es für Sie funktioniert.

+ (NSArray *)getRecordsForFetchRequest:(NSFetchRequest *)request inContext:(NSManagedObjectContext *)context 
{ 


@try 
{ 
    __weak __block NSError *error = nil; 

    __block __weak NSArray *results = nil; 


    [context performBlockAndWait:^{ 
     [context lock]; 

     results = [context executeFetchRequest:request error:&error]; 

     [context processPendingChanges]; 

     [context unlock]; 

    }]; 

    [self handleErrors:error]; 

    request = nil; 
    context = nil; 

    return results; 
} 
@catch (NSException *exception) 
{ 
    if([exception.description rangeOfString:@"Can only use -performBlockAndWait: on an NSManagedObjectContext that was created with a queue"].location!=NSNotFound) 
    { 
     NSError *error = nil; 

     [context lock]; 

     __weak NSArray *results = [context executeFetchRequest:request error:&error]; 

     [context processPendingChanges]; 

     [context unlock]; 

     [self handleErrors:error]; 

     request = nil; 
     context = nil; 

     return results; 
    } 

    return nil; 
} 

}

+0

Man sollte niemals die Ausnahmebehandlung in regulärem Code in Objective-C verwenden. Ausnahmen werden nur für logische oder interne Fehler ausgelöst und sollten nicht in normalen realen Situationen auftreten. Insbesondere sollten Sie hier zwei Methoden verwenden: eine für das Abrufen von Ergebnissen für einen mit einer privaten Warteschlange konfigurierten Kontext, die andere für andere Kontexte. – Frizlab