12

我目前正在后台线程中将一些文件写入磁盘,只需调用

dispatch_async(my_queue,^{
   [self writeToRoot:filename data:data];
};

- (BOOL)writeToRoot:(NSString *)path data:(NSData *)content
{
    NSString *fullPath = [[self rootPath] stringByAppendingPathComponent:path];

    NSString *pathWithoutFile = [fullPath stringByDeletingLastPathComponent];

    BOOL directoryExists = [[NSFileManager defaultManager] fileExistsAtPath:pathWithoutFile];

    if (!directoryExists) {
        NSError *error = nil;
        [[NSFileManager defaultManager] createDirectoryAtPath:pathWithoutFile
                                  withIntermediateDirectories:YES
                                                   attributes:nil error:&error];
        NSParameterAssert(error == nil);
    }

    return [content writeToFile:fullPath atomically:NO];
}

我这样做是因为它不会阻塞主线程。我的问题是如何确保线程安全。在执行此后台操作时,当我尝试通过调用从磁盘读取文件时会发生什么:

[NSData dataWithContentsOfFile:fullPath];

内容会被破坏吗?还是写操作会锁定文件,而读操作会等到写入完成?

4

2 回答 2

21

我倾向于dispatch_sync您的读取操作以my_queue确保线程安全(假设它是一个串行队列)。您还可以使用任何各种同步工具(例如锁或@synchronized指令),但鉴于您已经为文件交互设置了队列,使用该串行队列可能是最简单的。

这种使用队列来协调与共享资源的交互的技术在并发编程指南的消除基于锁的代码部分中进行了讨论。


顺便说一句,如果您正在后台队列中保存(这意味着保存操作可能足够慢以证明在后台执行此操作是合理的),那么确保您请求一点时间来完成操作可能是谨慎的如果应用程序本身在保存操作进行时被中断(即用户点击物理主页按钮、来电等)。您可以通过beginBackgroundTaskWithExpirationHandler在发送保存操作之前调用来执行此操作,并endBackgroundTask在完成时调用:

UIApplication *application = [UIApplication sharedApplication];

// get background task identifier before you dispatch the save operation to the background

UIBackgroundTaskIdentifier __block task = [application beginBackgroundTaskWithExpirationHandler:^{
    if (task != UIBackgroundTaskInvalid) {
        [application endBackgroundTask:task];
        task = UIBackgroundTaskInvalid;
    }
}];

// now dispatch the save operation

dispatch_async(my_queue, ^{

    // do the save operation here

    // now tell the OS that you're done

    if (task != UIBackgroundTaskInvalid) {
        [application endBackgroundTask:task];
        task = UIBackgroundTaskInvalid;
    }
});

这将确保您的保存操作有机会成功完成,即使应用程序被中断。

而且,正如 Jsdodgers 指出的那样,您可能还想执行原子写入。

于 2013-08-19T02:43:21.303 回答
2

由于您的代码现在是,是的,会有问题。这是因为您将其设置为不以原子方式传输:

return [content writeToFile:fullPath atomically:NO];

原子的意思是,它不是删除文件然后开始写入,而是将文件写入单独的临时文件位置。文件完全写入后,它会删除文件的旧版本(如果存在)并将新文件重命名为正确的名称。如果传输没有完成,什么都不会发生,临时文件应该被删除。

因此,如果您在该行中原子地更改为YES,那么调用该数据将返回旧数据,直到保存完成,之后的任何时候都会为您获取新数据。

所以要做到这一点,你会想要:

return [content writeToFile:fullPath atomically:YES];
于 2013-08-19T02:43:06.957 回答