无法对文件执行GCD i / o

时间:2014-08-26 11:40:39

标签: ios objective-c grand-central-dispatch

我需要异步加载和写入图像 - 但如果现在正在写入,我就无法访问该文件。为此,我想使用barrier_async来读取和同步读取文件。这是我的代码:

执行gcd操作的块方法的一部分:

    [NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler:
     ^(NSURLResponse *response, NSData *data, NSError *connectionError)
     {   
         [self.class writeData:data toFilePath:tileFilePathName completionHandler:^(NSError *error) {
             if (!error) {
                 dispatch_sync(dispatch_get_main_queue(), ^{
                     [[AxPanoramaDataManager sharedInstance].tilesNamesArray addObject:tileFileName];
                 });
                 [self.class readImagefromFilePath:tileFilePathName
                                 completionHandler:^(UIImage *image, NSError *error) {
                                     if (!error)
                                         dispatch_sync(dispatch_get_main_queue(), ^{
                                             completion(tileCoordValue, side, image, error);
                                         });
                                 }];
             }
         }];
     }];

和读/写方法:

+ (void) readImagefromFilePath: (NSString *) filePath
             completionHandler:(void (^)(UIImage* image, NSError* error)) handler
{
    dispatch_sync([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
        UIImage *tileImage = [UIImage imageWithContentsOfFile:filePath];
        dispatch_sync(dispatch_get_main_queue(), ^{
            handler(tileImage, nil);
            NSLog(@"Image %@ loaded from cash", tileImage);
        });
    });
}

+ (void) writeData: (NSData *) data
        toFilePath: (NSString *) filePath
 completionHandler:(void (^)(NSError* error)) handler
{
    dispatch_barrier_async([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
        [data writeToFile:filePath atomically:YES];
        dispatch_sync(dispatch_get_main_queue(), ^{
            handler(nil);
            NSLog(@"Data %@ wrote to the disk", data);
        });
    });
}

现在,当我尝试执行此方法时,应用程序已挂起。有什么帮助吗?

1 个答案:

答案 0 :(得分:4)

你自己陷入僵局。这是代码"展开"是一个电话。 (我将在下面分开。)

[NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    // Unroll: [self.class writeData: data toFilePath: filePath completionHandler: writeDataCompletion];
    dispatch_barrier_async(dataManagerQueue, ^{
        [data writeToFile:filePath atomically:YES];
        dispatch_sync(dispatch_get_main_queue(), ^{
            // Unroll: writeDataCompletion(nil);
            NSError* error = nil;
            if (!error) {
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [[AxPanoramaDataManager sharedInstance].tilesNamesArray addObject:tileFileName];
                });

                // Unroll: [self.class readImagefromFilePath:tileFilePathName completionHandler:readCompletion];
                dispatch_sync(dataManagerQueue, ^{
                    UIImage *tileImage = [UIImage imageWithContentsOfFile:filePath];
                    dispatch_sync(dispatch_get_main_queue(), ^{
                        // Unroll: readCompletion(tileImage, nil);
                        NSError* error = nil;
                        if (!error) {
                            dispatch_sync(dispatch_get_main_queue(), ^{
                                completion(tileCoordValue, side, tileImage, error);
                            });
                        }
                        NSLog(@"Image %@ loaded from cash", tileImage);
                    });
                });
            }
            NSLog(@"Data %@ wrote to the disk", data);
        });
    });
}];

现在让我们逐行跟踪它,注意我们在每个阶段所依赖的线程/队列:

[NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse *response, NSData *data, NSError *connectionError) {

好的,所以当你从-[NSURLConnection sendAsync...]进入回调主线程时,因为你将[NSOperationQueue mainQueue]传递给了queue:参数。

    // Unroll: [self.class writeData: data toFilePath: filePath completionHandler: writeDataCompletion];
    dispatch_barrier_async(dataManagerQueue, ^{

现在我们在dataManagerQueue处,在障碍区中,这意味着在我们从此区块返回之前,dataManagerQueue上无法运行任何其他内容。因为屏障调用是异步的,所以我们希望此时主线程/队列是空闲的。

        [data writeToFile:filePath atomically:YES];
        dispatch_sync(dispatch_get_main_queue(), ^{

现在我们重新回到主队列。请注意,由于这是使用dispatch_sync调用的,因此我们 仍位于dataManagerQueue的屏障区域中。

            // Unroll: writeDataCompletion(nil);
            NSError* error = nil;
            if (!error) {
                dispatch_sync(dispatch_get_main_queue(), ^{

我们已经在主队列中,因此dispatch_sync(dispatch_get_main_queue()将在这里陷入僵局。此时我们已经死在了水中,但是让我们继续前进,并假设dispatch_sync处理递归重新进入的时刻(它没有,但是...... 。)

                    [[AxPanoramaDataManager sharedInstance].tilesNamesArray addObject:tileFileName];
                });

                // Unroll: [self.class readImagefromFilePath:tileFilePathName completionHandler:readCompletion];
                dispatch_sync(dataManagerQueue, ^{

现在,请注意我们仍然在您提交给dataManagerQueue的屏障栏中,但我们正尝试通过dispatch_sync(dataManagerQueue, ...)提交另一个屏障。因此,如果我们上面的主队列上已经死锁,那么现在我们将在dataManagerQueue上陷入僵局。

                    UIImage *tileImage = [UIImage imageWithContentsOfFile:filePath];
                    dispatch_sync(dispatch_get_main_queue(), ^{

现在我们再次同步重新进入主队列

                        // Unroll: readCompletion(tileImage, nil);
                        NSError* error = nil;
                        if (!error) {
                            dispatch_sync(dispatch_get_main_queue(), ^{

再次 !!!

                                completion(tileCoordValue, side, tileImage, error);
                            });
                        }
                        NSLog(@"Image %@ loaded from cash", tileImage);
                    });
                });
            }
            NSLog(@"Data %@ wrote to the disk", data);
        });
    });
}];

简而言之,这里有多个死锁。您似乎在许多可能使用dispatch_sync的地方使用dispatch_async,但我无法知道还有什么其他内容让您认为所有这些完成都需要同步触发。根据您发布的代码,您可以首先将每个_sync调用转换为_async调用,而不会产生实质性的不良影响(即,此处发布的代码中唯一可见的效果是{{1 s会在不同的时间点火。)

另一个一般的经验法则是NSLog几乎总是一个坏主意。 (见detailed explanation)即使它有效"大部分时间"这是有问题的,因为你无法控制的事情(即在操作系统中)可能以一种可能导致死锁的方式与该模式交互。您可能希望在依赖dispatch_sync(dispatch_get_main_queue(), ...)成为dispatch_sync(dispatch_get_main_queue(), ...)的任何地方返工,然后将剩余的后台工作嵌套重新分配到后台队列。但一般来说,大多数主线程完成应该能够异步调度而不会出现问题。