NSOperationQueue becomes suspended at random?

2019-07-24 19:05发布

I have an app that uses NSOperationQueue intensively.

Sometimes I've noticed that some of the NSOperationQueues would "lock up" or enter a "isSuspended" state randomly even though my code never calls the setSuspended: method.

It's impossible to replicate and very hard to debug because whenever I hook the device up to Xcode to debug it, the app would reload and the bug would go away.

I added a lot of NSLogs at all possible points that might have a problem and just had to use the app for a few days until the bug reemerged.

Looking at the device system logs, I've discovered that [myOperationQueue operationCount] would increment but the opeations in queue will not execute.

I haven't tried manually setting "setSuspended:NO" yet but is that really necessary?

What could be causing this?

Here is a little bit of my code

The view controller that calls operations

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
        [self.operationQueue setMaxConcurrentOperationCount:2];

        self.sendOperationQueue = [[[NSOperationQueue alloc] init] autorelease];
        [self.sendOperationQueue setMaxConcurrentOperationCount:2];

        self.receiveOperationQueue = [[[NSOperationQueue alloc] init] autorelease];
        [self.receiveOperationQueue setMaxConcurrentOperationCount:1];
    }
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [operationQueue cancelAllOperations];
    [sendOperationQueue cancelAllOperations];
    [receiveOperationQueue cancelAllOperations];

    [operationQueue release];
    [sendOperationQueue release];
    [receiveOperationQueue release];
}

- (IBAction)sendMessage
{
    if(![chatInput.text isEqualToString:@""])
    {
        NSString *message = self.chatInput.text;
        SendMessageOperation *sendMessageOperation = [[SendMessageOperation alloc] initWithMatchData:matchData andMessage:message resendWithKey:nil];
        [self.sendOperationQueue addOperation:sendMessageOperation];
        [sendMessageOperation release];
    }
}

NSOperation subclass SendMessageOperation

- (id)initWithMatchData:(MatchData*)data andMessage:(NSString*)messageString resendWithKey:(NSString*)resendKey
{
self = [super init];

    if(self != nil)
    {
        if(data == nil || messageString == nil)
        {
            [self release];
            return nil;
        }
        appDelegate = (YongoPalAppDelegate *) [[UIApplication sharedApplication] delegate];

        context = [[NSManagedObjectContext alloc] init];
        [context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
        [context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:context];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeMainContextChanges:) name:NSManagedObjectContextDidSaveNotification object:appDelegate.managedObjectContext];

        self.matchData = (MatchData*)[context objectWithID:[data objectID]];
        matchNo = [[matchData valueForKey:@"matchNo"] intValue];
        partnerNo = [[matchData valueForKey:@"partnerNo"] intValue];

        self.message = messageString;
        self.key = resendKey;

        apiRequest = [[APIRequest alloc] init];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    self.matchData = nil;
    self.message = nil;
    self.key = nil;
    [context release];
    [apiRequest release];
    [super dealloc];
}

- (void)start
{
    if([self isCancelled] == YES)
    {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    else
    {
        [self willChangeValueForKey:@"isExecuting"];
        executing = YES;
        [self main];
        [self didChangeValueForKey:@"isExecuting"];
    }
}

- (void)main
{
    @try
    {
        NSAutoreleasePool *pool = [NSAutoreleasePool new];

        bool taskIsFinished = NO;
        while(taskIsFinished == NO && [self isCancelled] == NO)
        {
            NSDictionary *requestData = nil;

            if(key == nil)
            {
                requestData = [self sendMessage];
            }
            else
            {
                requestData = [self resendMessage];
            }

            NSDictionary *apiResult = nil;
            if(requestData != nil)
            {
                apiResult = [self sendMessageToServer:requestData];
            }

            if(apiResult != nil)
            {                
                [[NSNotificationCenter defaultCenter] postNotificationName:@"shouldConfirmSentMessage" object:nil userInfo:apiResult];
            }

            taskIsFinished = YES;
        }

        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
        finished = YES;
        executing = NO;
        [self didChangeValueForKey:@"isFinished"];
        [self didChangeValueForKey:@"isExecuting"];

        [pool drain];
    }
    @catch (NSException *e)
    {
        NSLog(@"Exception %@", e);
    }
}

- (BOOL)isConcurrent
{
    return YES;
}

- (BOOL)isFinished
{
    return finished;
}

- (BOOL)isExecuting
{
    return executing;
}

- (void)mergeContextChanges:(NSNotification *)notification
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"mergeChatDataChanges" object:nil userInfo:[notification userInfo]];
}

- (void)mergeMainContextChanges:(NSNotification *)notification
{
    NSSet *updated = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
    for(NSManagedObject *thing in updated)
    {
        [[context objectWithID:[thing objectID]] willAccessValueForKey:nil];
    }

    [context mergeChangesFromContextDidSaveNotification:notification];
}

3条回答
我只想做你的唯一
2楼-- · 2019-07-24 19:30

From the NSOperationQueue's isSuspended method documentation:

If you want to know when the queue’s suspended state changes, configure a KVO observer to observe the suspended key path of the operation queue.

Another idea that you can implement in parallel with the observer is to create a subclass of NSOperationQueue that redefines the setSuspended: method. In the redefined version you can set a breakpoint if you are running on the debugger, or print a stacktrace to the log if you are running w/o debugger.

Hope this helps.

查看更多
爷、活的狠高调
3楼-- · 2019-07-24 19:48

You're using:

setMaxConcurrentOperationCount:X

My hunch is that X operations in the queue have not finished, therefore causing any subsequent operations added to the queue to not run until this is the case.

查看更多
来,给爷笑一个
4楼-- · 2019-07-24 19:54

take a look at ASIHTTPRequestConfig.h, it has some flags that you can turn on to see what is really happening behind the scenes.

查看更多
登录 后发表回答