2013-04-16 2 views
4

다중 스레드 시나리오에서 여러 NSManagedObjectContexts & 시나리오에서 발생하는 것과 동일한 교착 상태 문제가 발생합니다. 일부 뷰 컨트롤러에서 내 앱은 백그라운드 스레드를 사용하여 웹 서비스에서 데이터를 가져오고 동일한 스레드에서 데이터를 저장합니다. 저장하지 않고 더 이상 진행하지 않는 것이 좋은 경우 (예 : 양식이 '다음'에 도달 할 때 양식에서 값을 지속하는 경우) 저장이 주 스레드에서 수행됩니다. AFAIK이 이론이 아무 문제 없을해야하지만, 때때로 나는 교착 상태가CoreData 여러 스레드 스레드 교착 상태

if (![moc save:&error]) 

에 대한 호출에서 발생 할 수 있습니다 ... 이것은 항상 배경에 수 교착 상태가 발생했을 때의 저장 스레드 것으로 보인다. 그것은 모든 부름에 일어나는 것이 아닙니다. 사실 그것은 꽤 정반대입니다. 몇 분 동안 앱을 사용해야 만합니다.

필자는 Apple 문서 등을 찾을 수있는 모든 게시물을 읽었으며 권장 사항을 따르고 있음을 확신합니다. 구체적으로, 여러 MOC/스레드로 작업하는 것에 대한 나의 이해는 다음과 같습니다.

  • 각 스레드마다 자체 MOC가 있어야합니다.
  • 스레드의 MOC는 해당 스레드에서 만들어야합니다 (한 스레드에서 다른 스레드로 전달되지 않음).
  • NSManagedObject를 전달할 수는 없지만 NSManagedObjectID를 사용하면 ID를 사용하여 다른 MOC를 사용하여 NSManagedObject를 부 풀릴 수 있습니다.
  • 둘 다 동일한 PersistentStoreCoordinator를 사용하는 경우 한 MOC에서 변경 한 내용을 다른 항목과 병합해야합니다.

얼마 다시 나는 this SO thread에 MOC 헬퍼 클래스에 대한 몇 가지 코드를 가로 질러 와서 그렇게 내 모든 MOC 상호 작용이 그 통해 지금 사용하기 쉽게 이해할 수있는 아주 편리한 것을 발견했다.

NSManagedObjectID *parentObjectID = [parent objectID]; 

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
    dispatch_async(queue, ^{ 
     // GET BACKGROUND MOC 
     NSManagedObjectContext *backgroundContext = [ManagedObjectContextHelper managedObjectContext]; 

     Parent *backgroundParent = (Parent*)[backgroundContext objectWithID:parentObjectID]; 
     // HIT THE WEBSERVICE AND PUT THE RESULTS IN THE PARENT OBJECT AND ITS CHILDREN, THEN SAVE... 
[ManagedObjectContextHelper commit]; 

     dispatch_sync(dispatch_get_main_queue(), ^{ 

      NSManagedObjectContext *mainManagedObjectContext = [ManagedObjectContextHelper managedObjectContext]; 

      parent = (Parent*)[mainManagedObjectContext objectWithID:parentObjectID]; 
}); 
    }); 

오류의 conflictList이가 뭔가 제안하는 것 같다가 교착 것으로 보인다 곳에있다

#import "ManagedObjectContextHelper.h" 

@implementation ManagedObjectContextHelper 

+(void)initialize { 
    [[NSNotificationCenter defaultCenter] addObserver:[self class] 
              selector:@selector(threadExit:) 
               name:NSThreadWillExitNotification 
               object:nil]; 
} 

+(void)threadExit:(NSNotification *)aNotification { 
    TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]]; 
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts; 

    [managedObjectContexts removeObjectForKey:threadKey]; 
} 

+(NSManagedObjectContext *)managedObjectContext { 
    TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSManagedObjectContext *moc = delegate.managedObjectContext; 

    NSThread *thread = [NSThread currentThread]; 

    if ([thread isMainThread]) { 
     [moc setMergePolicy:NSErrorMergePolicy]; 
     return moc; 
    } 

    // a key to cache the context for the given thread 
    NSString *threadKey = [NSString stringWithFormat:@"%p", thread]; 

    // delegate.managedObjectContexts is a mutable dictionary in the app delegate 
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts; 

    if ([managedObjectContexts objectForKey:threadKey] == nil) { 
     // create a context for this thread 
     NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] init]; 
     [threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]]; 
     [threadContext setMergePolicy:NSErrorMergePolicy]; 
     // cache the context for this thread 
     NSLog(@"Adding a new thread:%@", threadKey); 
     [managedObjectContexts setObject:threadContext forKey:threadKey]; 
    } 

    return [managedObjectContexts objectForKey:threadKey]; 
} 

+(void)commit { 
    // get the moc for this thread 
    NSManagedObjectContext *moc = [self managedObjectContext]; 

    NSThread *thread = [NSThread currentThread]; 

    if ([thread isMainThread] == NO) { 
     // only observe notifications other than the main thread 
     [[NSNotificationCenter defaultCenter] addObserver:[self class]             selector:@selector(contextDidSave:) 
                name:NSManagedObjectContextDidSaveNotification 
                object:moc]; 
    } 

    NSError *error; 
    if (![moc save:&error]) { 
     NSLog(@"Failure is happening on %@ thread",[thread isMainThread][email protected]"main":@"other"); 

     NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey]; 
     if(detailedErrors != nil && [detailedErrors count] > 0) { 
      for(NSError* detailedError in detailedErrors) { 
       NSLog(@" DetailedError: %@", [detailedError userInfo]); 
      } 
     } 
     NSLog(@" %@", [error userInfo]); 

    } 

    if ([thread isMainThread] == NO) { 
     [[NSNotificationCenter defaultCenter] removeObserver:[self class]              name:NSManagedObjectContextDidSaveNotification 
                 object:moc]; 
    } 
} 

+(void)contextDidSave:(NSNotification*)saveNotification { 
    TDAppDelegate *delegate = (TDAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSManagedObjectContext *moc = delegate.managedObjectContext; 

    [moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
          withObject:saveNotification 
         waitUntilDone:NO]; 
} 
@end 

멀티 스레드 비트의 조각이다 : 여기에 전체 내 ManagedObjectContextHelper 클래스입니다 부모 개체의 OBJECTID과는 : 나는 refreshObject에 넣어 시도했습니다

conflictList =  (
      "NSMergeConflict (0x856b130) for NSManagedObject (0x93a60e0) with objectID '0xb07a6c0 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Child/p4>' 
with oldVersion = 21 and newVersion = 22 
and old object snapshot = {\n parent = \"0xb192280 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n} 
and new cached row = {\n parent = \"0x856b000 <x-coredata://B7371EA1-2532-4D2B-8F3A-E09B56CC04F3/Parent/p3>\";\n name = \"New Child\";\n returnedChildId = 337046373;\n time = 38;\n}" 
     ); 

이론 인 일에, 최대한 빨리이 MOC 잡아 왔으로 호출 에서 이전에 사용한 MOC 인 경우 (예 : 이전에 주 스레드에서 MOC를 사용했고 이것이 도우미 클래스가 제공하는 것과 동일한 것일 수 있습니다.) 그러면 다른 스레드의 저장은 명시 적으로 새로 고쳐야 함을 의미합니다. 하지만 아무런 차이가 없었습니다. 계속 길게 누르면 계속해서 교착 상태가됩니다.

누구에게 아이디어가 있습니까?

편집 : 모든 예외에 브레이크 포인트가 설정된 경우 디버거는 if (![moc save:&error]) 행에서 자동으로 일시 중지되므로 재생/일시 중지 버튼이 이미 일시 중지되어 재생 삼각형을 표시하고 있습니다. 모든 예외에 대한 중단 점을 사용하지 않으면 병합 정책이 현재 NSErrorMergePolicy로 설정되어 있기 때문에 실제로 충돌을 기록하고 계속됩니다. 따라서 스레드에서 실제로 교착 상태에 있다고 생각하지 않습니다. 일시 중지 된 상태에서 두 스레드의 상태가 Here's a screehshot입니다.

+0

앱이 교착 상태가되면 어떤 방법으로 메인 스레드에 멈 춥니 까? – lassej

+0

@lassej 다음은 Thread1의 출력입니다 : libsystem_kernel.dylib'mach_msg_trap : 0x98030c18 : movl $ 4294967265, % eax 0x98030c1d : calll 0x9803449a; _sysenter_trap 0x98030c22 : ret 0x98030c23 : nop – bobsmells

+0

나는 그것이 당신이 필요로하는 것이 확실하지 않습니다. Thread6은 [NSManagedObjectContext save]에 있습니다. Thread1이 실제로 차단되었는지는 잘 모르겠지만 제대로 이해한다면 두 스레드의 MOC에서 교착 상태가됩니다. – bobsmells

답변

3

전혀 사용하지 않는 것이 좋습니다.첫째, iOS4에만 국한되지 않는 한 이전 방법이 아닌 MOC 동시성 유형을 사용해야합니다. iOS 5 (중첩 된 컨텍스트의 경우)에도 불구하고 performBlock 접근 방식이 훨씬 효과적입니다.

또한 dispatch_get_global_queue은 동기화에 사용할 수없는 동시 대기열을 제공합니다.

세부 사항은 여기에서 찾을 수 있습니다 당신은 MOCS을 관리하려고 수동으로 스레딩되어

http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html

편집. 원한다면 할 수 있지만 길에는 용들이 있습니다. 그렇기 때문에 코어 데이터를 여러 스레드에서 사용할 때 오류가 발생할 가능성을 최소화하기 위해 새로운 방식이 만들어졌습니다. 코어 데이터로 수동 스레드 관리를 볼 때마다 항상 첫 번째 방법으로 변경하도록 제안합니다. 그러면 대부분의 오류가 즉시 제거됩니다.

문제를 묻고 있음을 알기 위해 수동으로 MOC와 스레드를 매핑하는 것 외에는 많이 볼 필요가 없습니다. 해당 설명서를 다시 읽고 바로 사용하십시오 (performBlock 사용).

+0

감사합니다. @ Jody Hagins, 당신이 말하는 내용에 대해서는 명확하지 않습니다. 나는 그 링크를 읽었고 AFAIK는 내가하고있는 일들과 거의 일치한다. 그래서 나는 "오래된 방법"을하고있다. MOC 동시성 유형과의 차이점은 무엇입니까? MOC 동시성 유형이란 무엇입니까? – bobsmells

+0

Ok, 죄송합니다, MOC 동시성 유형에 대해 이해하지만이 방법이 여전히 작동하지 않는 이유를 모르겠습니다. 글로벌 대기열 및 동기화에 대한 귀하의 요점과 관련하여, 내 응용 프로그램은 동시에 여러 스레드에서 실행되는 데이터베이스 작업을 수행하지 않으므로 문제가되어서는 안된다고 생각합니까? GCD 만 사용하므로 긴 웹 서비스를 히트하면서 UI (예 : 진행률 표시 줄)를 업데이트 할 수 있습니다. – bobsmells

+0

감사합니다, 페니는 떨어지기 시작하고 있습니다 ...하지만 문제는 다른 이유로 (내 경우에는 GCD를 통해) 멀티 스레딩을 사용해야한다는 것입니다 - 주로 장기 실행 웹 서비스 호출, 어떻게 performBlock을합니까? performBlockAndWait가 그것에 맞습니까? 이러한 MOC의 메소드를 감안할 때 performBlock 내에 웹 서비스 호출을 넣은 다음 결과 데이터를 블록 내부에 보존 할 수 있습니까? 이러한 데이터 변경에 의존하는 UI 업데이트는 어떻게됩니까? 외장 내부에 mainMOC performBlock이있는 외부 nonMainMOC performBlock이있는 경우입니까? – bobsmells

관련 문제