主队列上的dispatch_sync和executeFetchRequest:error:之间的死锁问题

8

我正在主队列上使用dispatch_sync()来调用一个块。在这个块中,最终会调用executeFetchRequest:error:。有时,这会导致死锁。

以下是线程1的信息,显示了在主线程上调用的块以及对executeFetchRequest:error:的调用:

#0  0x981f3876 in __psynch_mutexwait ()
#1  0x97a016af in pthread_mutex_lock ()
#2  0x0135be32 in -[_PFLock lock] ()
#3  0x0135be0a in -[NSPersistentStoreCoordinator lock] ()
#4  0x01371d1c in -[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore] ()
#5  0x013702c0 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#6  0x0000a701 in -[NSManagedObjectContext(Convenience) fetchObjectsForEntityName:onlyIDs:withPredicate:] at /Users/mike/Dropbox/src/Tracky-iPhone/Tracky/Tracky/NSManagedObjectContext(Convenience).m:50
#7  0x00065270 in +[NSManagedObject(ContextAdditions) contextForID:managedObjectContext:] at /Users/mike/Dropbox/src/Tracky-iPhone/Tracky/Tracky/NSManagedObject+ContextAdditions.m:40
#8  0x0006597e in __85+[NSManagedObject(ContextAdditions) createContextIfNeededForID:managedObjectContext:]_block_invoke_0 at /Users/mike/Dropbox/src/Tracky-iPhone/Tracky/Tracky/NSManagedObject+ContextAdditions.m:55
#9  0x00065aad in __85+[NSManagedObject(ContextAdditions) createContextIfNeededForID:managedObjectContext:]_block_invoke_050 at /Users/mike/Dropbox/src/Tracky-iPhone/Tracky/Tracky/NSManagedObject+ContextAdditions.m:64
#10 0x023cf8d9 in _dispatch_barrier_sync_f_slow_invoke ()
#11 0x023d0509 in _dispatch_main_queue_callback_4CF ()
#12 0x01ae9803 in __CFRunLoopRun ()
#13 0x01ae8d84 in CFRunLoopRunSpecific ()
#14 0x01ae8c9b in CFRunLoopRunInMode ()
#15 0x019b47d8 in GSEventRunModal ()
#16 0x019b488a in GSEventRun ()
#17 0x004ba626 in UIApplicationMain ()

我可以看到executeFetchRequest:error:正在阻塞NSPersistentStoreCoordinator的锁定,但我不知道当前是谁锁定了它。
原始的dispatch_sync()来自于另一个线程,以下是该线程的情况,如果有必要,请参考:
Thread 18, Queue : (null)
#0  0x981f1c5e in semaphore_wait_trap ()
#1  0x023d1bda in _dispatch_thread_semaphore_wait ()
#2  0x023d0cb2 in _dispatch_barrier_sync_f_slow ()
#3  0x023d0e0f in dispatch_barrier_sync_f ()
#4  0x023d0f4c in _dispatch_sync_slow ()
#5  0x0006563a in +[NSManagedObject(ContextAdditions) createContextIfNeededForID:managedObjectContext:] at /Users/mike/Dropbox/src/Tracky-iPhone/Tracky/Tracky/NSManagedObject+ContextAdditions.m:63
#6  0x0006e2ee in __109-[ItemFetcher createOrConfigureObjectWithDescriptor:withContext:jsonObjectIDKey:modelObjectIDKey:entityName:]_block_invoke_0 at /Users/mike/Dropbox/src/Tracky-iPhone/Tracky/Tracky/ItemFetcher.m:78
#7  0x023ce330 in _dispatch_call_block_and_release ()
#8  0x023cff0c in _dispatch_queue_drain ()
#9  0x023cfcb4 in _dispatch_queue_invoke ()
#10 0x023cf402 in _dispatch_worker_thread2 ()
#11 0x97a04b24 in _pthread_wqthread ()

以下是原始调度的代码:
+ (Context *) createContextIfNeededForID: (NSString *) contextID managedObjectContext:(NSManagedObjectContext *) moc
    {
    // See if this context is in the main MOC. This call needs to happen synchronously on the main queue, if we're
    //  not on the main queue
    Context * (^contextFromMainMOCBlock)(void) = 
        ^ `Context` * {
        // We are guaranteed to be in the main here; look at how this block is invoked.
        return [self contextForID:contextID managedObjectContext:[UIApplication trackyAppDelegate].managedObjectContext] ;
        } ;

    __block Context *contextFromMainMOC = nil ;

    if( [UIApplication trackyAppDelegate].managedObjectContext == moc )
        contextFromMainMOC = contextFromMainMOCBlock() ;
    else
        dispatch_sync(dispatch_get_main_queue(), ^{
            contextFromMainMOC = contextFromMainMOCBlock() ; // <-- here
            }) ;
…

contextForID:contextID managedObjectContext:实际上在调用executeFetchRequest:error:之前并不会对核心数据进行任何操作。

更新

以下是其余的堆栈跟踪。据我所知,它们并没有执行任何有趣的操作,但我可能是错误的。

Thread 3, Queue : (null)
#0  0x981f490a in kevent ()
#1  0x023d0372 in _dispatch_mgr_invoke ()
#2  0x023cebe1 in _dispatch_mgr_thread ()
Thread 5 WebThread, Queue : (null)
#0  0x981f1c22 in mach_msg_trap ()
#1  0x981f11f6 in mach_msg ()
#2  0x01b8610a in __CFRunLoopServiceMachPort ()
#3  0x01ae95d5 in __CFRunLoopRun ()
#4  0x01ae8d84 in CFRunLoopRunSpecific ()
#5  0x01ae8c9b in CFRunLoopRunInMode ()
#6  0x04776420 in _ZL12RunWebThreadPv ()
#7  0x97a02ed9 in _pthread_start ()
Thread 6 com.apple.NSURLConnectionLoader, Queue : (null)
#0  0x981f1c22 in mach_msg_trap ()
#1  0x981f11f6 in mach_msg ()
#2  0x01b8610a in __CFRunLoopServiceMachPort ()
#3  0x01ae95d5 in __CFRunLoopRun ()
#4  0x01ae8d84 in CFRunLoopRunSpecific ()
#5  0x01ae8c9b in CFRunLoopRunInMode ()
#6  0x00ebae30 in +[NSURLConnection(Loader) _resourceLoadLoop:] ()
#7  0x00dcc4d6 in -[NSThread main] ()
#8  0x00dcc447 in __NSThread__main__ ()
#9  0x97a02ed9 in _pthread_start ()
Thread 10 com.apple.CFSocket.private, Queue : (null)
#0  0x981f3b42 in select$DARWIN_EXTSN ()
#1  0x01b1a7cb in __CFSocketManager ()
#2  0x97a02ed9 in _pthread_start ()

这里是 contextID:managedObjectContext:, 它是在 dispatch_sync() 中调用的方法:

+ (Context *) contextForID: (NSString *) contextID managedObjectContext:(NSManagedObjectContext *) moc
    {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"cid == %@", contextID] ;
    NSSet *contexts = [moc fetchObjectsForEntityName:@"Context" onlyIDs:NO withPredicate:predicate] ;

    // Integrity check
    NSAssert1(contexts.count < 2, @"More than one context with the same CID exists: %@" , contexts) ;

    return [contexts anyObject] ;
    }

fetchObjectsForEntityName:onlyIDs:withPredicate: 看起来像这样:

- (NSSet *)fetchObjectsForEntityName:(NSString *)newEntityName onlyIDs: (BOOL) onlyIDs
    withPredicate:(id)stringOrPredicate, ...
    {
    NSEntityDescription *entity = [NSEntityDescription
        entityForName:newEntityName inManagedObjectContext:self];

    NSAssert1( entity != nil , @"entity not found for \"%@\"" , newEntityName ) ;

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entity];
    [request setIncludesPropertyValues:!onlyIDs] ;

    if (stringOrPredicate)
        {
        NSPredicate *predicate;
        if ([stringOrPredicate isKindOfClass:[NSString class]])
            {
            va_list variadicArguments;
            va_start(variadicArguments, stringOrPredicate);
            predicate = [NSPredicate predicateWithFormat:stringOrPredicate
                arguments:variadicArguments];
            va_end(variadicArguments);
            }
        else
            {
            NSAssert2([stringOrPredicate isKindOfClass:[NSPredicate class]],
                @"Second parameter passed to %s is of unexpected class %@",
                sel_getName(_cmd), NSStringFromClass(stringOrPredicate));
            predicate = (NSPredicate *)stringOrPredicate;
            }
        [request setPredicate:predicate];
        }

    NSError *error = nil;
    NSArray *results ;
    @try {
         results = [self executeFetchRequest:request error:&error];
    }
    @catch (NSException *exception) {
        NSLog(@"Exception caught: %@" , exception) ;
    }

    if (error != nil)
        {
        [NSException raise:NSGenericException format:@"%@",[error description]];
        }

    return [NSSet setWithArray:results];
    }

4
尝试使用dispatch_async(),这段代码就不会一直等待了。 - Paul.s
1
其他线程在做什么?此外,我有点同意Paul.s的观点。GCD的哲学是尽可能避免同步调度。问题在于,您设计了一个API,该API将数据返回给其调用者,然后调用者对其进行操作,因此必须是同步的。相反,应该有一个块来处理数据,当数据可用时。调用者可以传递它,从而使整个操作异步化。 - Ken Thomases
你解决过这个问题吗,@leftspin?我也因为死锁而得到了相同的__psync_mutexwait错误,并且我正在使用dispatch_async()。你能分享一下你是如何解决它的吗? - epologee
不好意思,问题仍未解决。我不得不退出并尝试不同的架构。 - leftspin
类似的问题(这个有帮助吗):http://stackoverflow.com/questions/19576280/core-data-freeze-at-fetch-request-deadlock?rq=1 - Anand
显示剩余2条评论
2个回答

0

检查你的NSManagedObjectContextconcurrencyType。看起来你的上下文具有NSMainQueueConcurrencyType,它在主线程中运行所有请求。来自Apple reference

NSMainQueueConcurrencyType

指定上下文将与主队列相关联。

如果我没错的话,你需要将类型更改为NSPrivateQueueConcurrencyType


0
为了提供更多的帮助,您需要展示堆栈跟踪中所有方法的代码以及行号。这样,它可以进行相关性分析。
此外,看起来您有许多线程。我认为您将不得不检查其他线程以查看发生了什么。
在调用同步操作时必须非常小心,特别是如果它可能从其他同步操作中调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接