无法对文件执行 GCD I/O



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

块方法中执行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);
        });
    });
}

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

您正在死锁。这是一个"展开"为单个调用的代码。(我在下面把它拆开。)

[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上运行。因为barrier调用是异步的,所以我们希望主线程/队列此时是空闲的。

        [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调用,而不会产生实质性的不良影响(即,从此处发布的代码中可以看到的唯一影响是NSLog将在不同时间启动。)

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

最新更新