异步调度队列完成执行后如何执行所有其他任务

时间:2015-11-20 07:04:59

标签: objective-c multithreading macos asynchronous deadlock

我有一个PDF文档,它在删除,裁剪,旋转等页面上有一些操作。

所以当我点击删除按钮并点击保存(当前主题:主线程)

-(void)save
{
    // -- process deleted pages first
            for( NSString* deletedPageId in self.deletedPageIdList )
            {
                [self.pdfCoordinator removePageWithId:deletedPageId];
            }
            // -- wait for all pages to delete before continuing
            [self.pdfCoordinator waitUntilAllOperationsAreFinished];
            // few lines of code after this should run only when the above code finishes its execution
            // code to save the changes in the pdf to managedObject context

}

removePageWithId的代码:

- (void) removePageWithId:(NSString*)pageId
{
    NRMPDFOperation *op = [[NRMPDFOperation alloc] initWithNRMPDF:self
                                                           pageId:pageId
                                                         selector:@selector(removePageOpImpl:)
                                                             args:nil];
    [self addOperation:op];
    [op release];

}

上面的代码创建了一个操作,并将其添加到每次删除页面的操作队列中

removePageOpImpl的代码:

- (NSError *)removePageOpImpl:(NRMPDFOperation *)op
{
    NSError* error = [self loadDocument];
    if( !error )
    {
        NSUInteger index = [self pageIndexForId:[op pageId]];
        if( index < [self pageCount] )
        {
                    [[self pdfDocument] removePageAtIndex:index];
                    [[self mutablePageIdList] removeObjectAtIndex:index];
                    [self updatePageLabelsFromIndex:index];
                    [self updateChangeCount:NSChangeDone];
                    self.contentsChanged = YES;
        }
        else
        {
            // TODO: error
        }
    }
    return error;
}

在removePageOpImpl:方法中的代码行 [[self pdfDocument] removePageAtIndex:index];在主线程上内部执行一些任务(但是我们让主线程等到这个操作完成)。这导致死锁。

我尝试在removePageOpImpl中执行代码:在异步调度队列中避免死锁。贝洛是那个代码

- (NSError *)removePageOpImpl:(NRMPDFOperation *)op
{
    NSError* error = [self loadDocument];
    if( !error )
    {
        NSUInteger index = [self pageIndexForId:[op pageId]];
        if( index < [self pageCount] )
        {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[self pdfDocument] removePageAtIndex:index];
                    [[self mutablePageIdList] removeObjectAtIndex:index];
                    [self updatePageLabelsFromIndex:index];
                    [self updateChangeCount:NSChangeDone];
                    self.contentsChanged = YES;
                    });
        }
        else
        {
            // TODO: error
        }
    }
    return error;
}

现在我摆脱了僵局。但另一个问题是将上面的代码放到异步块中,这个代码应该在此任务执行之前运行,因为我的应用程序没有按预期运行。

waitUntilAllOperationsAreFinished方法中的代码

- (void) addOperation:(NRMPDFOperation*)operation
{
    [_operationSet addObject:operation];
    [[self operationQueue] addOperation:operation];
}


- (void) waitUntilAllOperationsAreFinished
{
    [[self operationQueue] waitUntilAllOperationsAreFinished];
}



 - (NSOperationQueue *)operationQueue
{
    if( !_operationQueue )
    {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue setMaxConcurrentOperationCount:1];
        _operationQueue = queue;
    }
    return _operationQueue;
}

这是原始Save方法的样子:

- (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo
{
    // TODO: don't just ignore saveOperation

    __block BOOL success = YES;
    __block NSError *error = nil;

    /* write back field changes */  
    if ([item hasChangesInEditFieldsetFor:@"values"] )
    {
        //Some code
    }


    if( self.isPDFEdited )
    {

        // -- process deleted pages first
            for( NSString* deletedPageId in self.deletedPageIdList )
            {
                [self.itemPDFCoordinator removePageWithId:deletedPageId];
            }
            // -- wait for all pages to delete before continuing
            [self.itemPDFCoordinator waitUntilAllOperationsAreFinished];


            // -- replace the search text any pages we deleted
            if( [self.deletedPageIdList count] )
            {
                [self.item setValue:[self.editPDF string] forKeyPath:@"dict.values._searchText"];
            }

        NSMutableDictionary* originalRotations = [NSMutableDictionary dictionaryWithCapacity:
                                                  [self.itemPDFCoordinator pageCount]];
        for( NSString* pageId in self.itemPDFCoordinator.pageIdList )
        {
            NSInteger rotation = [[self.itemPDFCoordinator pageForId:pageId] rotation];
            [originalRotations setObject:[NSNumber numberWithInteger:rotation] forKey:pageId];
        }

        // -- now process actions on remaining pages (crop, rotate, and convert to b&w)
        BOOL didCropAnyPages = NO;
        NSMutableArray* convertToBwJobs = [NSMutableArray array];
        for( NSString* pageId in [self.pageActionDict allKeys] )
        {
            NSArray* actions = [self.pageActionDict objectForKey:pageId];
            for( NSDictionary* action in actions )
            {
                NSNumber* rotationNumber = [action objectForKey:@"rotation"];
                NSValue* cropRectVal = [action objectForKey:@"cropRect"];
                NSNumber* convertToBlackAndWhite = [action objectForKey:@"convertToBlackAndWhite"];

                if( rotationNumber )
                {
                    [self.itemPDFCoordinator rotateByDegrees:[rotationNumber integerValue]
                                               forPageWithID:pageId];
                }
                else if( cropRectVal )
                {
                    [self.itemPDFCoordinator setNormalizedBounds:[cropRectVal rectValue]
                                                          forBox:kPDFDisplayBoxCropBox
                                                   forPageWithID:pageId];
                    // -- set a flag so we know to recrop the entire document
                    didCropAnyPages = YES;
                }
                else if( [convertToBlackAndWhite boolValue] )
                {
                    NSUInteger pageIndex = [self.itemPDFCoordinator pageIndexForId:pageId];
                    NRMJob* job = [NRMAppJobFactory convertToBlackAndWhiteJobForItem:self.item
                                                                           pageIndex:pageIndex];
                    [convertToBwJobs addObject:job];
                }
            }
        }

        // -- reapply crop box to any cropped pages
        if( didCropAnyPages )
        {
            [self.itemPDFCoordinator applyCropBoxToAllPages];
        }

        [self.itemPDFCoordinator waitUntilAllOperationsAreFinished];

        for( NRMJob* job in convertToBwJobs )
        {
            if( ![[self.masterDocument docjob] addJob:job forItem:self.item error:&error] )
                [NSApp presentError:error];
            else
                [job waitUntilDone];
        }

        // -- make sure document attributes are updated
        NSDictionary *docDict = [self.itemPDFCoordinator documentAttributes];
        NSDictionary *newDict = [(NRMItem *)item updateDocumentAttributes:docDict];
        if (![newDict isEqualToDictionary:docDict])
            [self.itemPDFCoordinator setDocumentAttributes:newDict];

        [self.itemPDFCoordinator waitUntilAllOperationsAreFinished];

        // -- check if we need to reprocess any pages
        for( NSString* pageId in self.itemPDFCoordinator.pageIdList )
        {
            NSInteger oldRotation = [[originalRotations objectForKey:pageId] integerValue];
            NSInteger newRotation = [[self.itemPDFCoordinator pageForId:pageId] rotation];
            if( oldRotation != newRotation )
            {
                // -- if it's an image page and we already have OCR data for it, we should reanalyze
                NSUInteger pageIndex = [self.itemPDFCoordinator pageIndexForId:pageId];
                BOOL isPageImage = [self.itemPDFCoordinator isImagePageAtIndex:pageIndex DPI:NULL];
                if( isPageImage && [item OCRDataForPageIndex:pageIndex] )
                {
                    NRMJob* job = [NRMAppJobFactory reprocessPageJobForItem:self.item 
                                                                  pageIndex:pageIndex];
                    [[NSNotificationCenter defaultCenter] addObserver:self
                                                             selector:@selector(reanalyzeJobFinished:)
                                                                 name:kNRMJobFinishedNotification
                                                               object:job];
                    success = [[self.masterDocument docjob] addJob:job forItem:self.item error:&error];
                    if( !success )
                    {
                        if( error )
                            [self presentError:error];
                    }
                        //goto bail;
                }
            }
        }

        //Force Save of PDF to Disk
        [self.itemPDFCoordinator setManagedObjectContext:[item managedObjectContext]];
        [self.itemPDFCoordinator saveChanges];
}

    if( success )
    {
        [self updateChangeCount:NSChangeCleared];
        if( self.isPDFEdited )
        {
            // !CLEARSPLIT! please do not remove this comment
            [self.item setValue:nil forKeyPath:@"dict.values._splitId"];

            if( ![self loadPDFForItem:item error:&error] )
                goto bail;
        }
    }

bail:
    if( error )
        [self presentError:error];

    if( delegate )
    {
        /* signature: 
         - (void)document:(NSDocument *)document didSave:(BOOL)didSaveSuccessfully  contextInfo:(void  *)contextInfo;
         */
        objc_msgSend( delegate, didSaveSelector, self, (error ? NO : YES), contextInfo );
    }
}

任何人都可以建议我如何摆脱这个问题。

1 个答案:

答案 0 :(得分:0)

我建议指定一个额外的完成操作,而不是调用waitUntilAllOperationsAreFinished(阻塞一个线程),你可以根据其他操作完成操作:

-(void)save {
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        // whatever you want to do when it's done
    }];

    for (NSString* deletedPageId in self.deletedPageIdList) {
        NSOperation *operation = [self.pdfCoordinator removePageWithId:deletedPageId];
        [completionOperation addDependency:operation];
    }

    // add this completionOperation to a queue, but it won't actually start until the other operations finish

    [queue addOperation:completionOperation];
}

显然,这假设您更改removePageWithId以返回它添加到队列中的操作:

- (NSOperation *)removePageWithId:(NSString*)pageId {
    NRMPDFOperation *operation = [[NRMPDFOperation alloc] initWithNRMPDF:self
                                                                  pageId:pageId
                                                                selector:@selector(removePageOpImpl:)
                                                                    args:nil];
    [self addOperation:operation];
    [operation release];
    return operation;
}

这样,您不会阻止任何线程等待操作完成,而只是指定完成操作后要执行的操作。