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

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

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

        [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(), ...)几乎总是一个坏主意。 (请参阅detailed explanation)即使它在“大多数时间”都有效,也是有问题的,因为无法控制的事物(例如,在OS中)可能会与该模式交互,从而导致死锁。您可能想在依赖dispatch_sync(dispatch_get_main_queue(), ...)成为dispatch_async(dispatch_get_main_queue(), ... )的任何地方进行重做,然后将剩余的后台工作嵌套重新调度到后台队列中。但是一般来说,大多数主线程完成应该能够异步发送而没有问题。

关于ios - 无法对文件执行GCD I/O,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25504981/

10-11 00:34