我目前正在通过调用
在后台线程中将一些文件写入磁盘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];
内容会被破坏吗? 或者写操作会锁定文件,读操作会等到写完成吗?
答案 0 :(得分:20)
我倾向于dispatch_sync
对my_queue
进行读操作以确保线程安全(假设它是一个串行队列)。您也可以使用各种synchronization tools中的任何一个(例如lock或@synchronized
指令),但鉴于您已经为文件交互设置了队列,使用该串行队列可能是最简单的。
并发编程指南的Eliminating Lock-Based Code部分讨论了使用队列协调与共享资源交互的技术。
顺便说一句,如果你在后台队列中保存(这意味着保存操作可能足够慢以证明在后台进行操作),那么确保你请求一点时间来完成它可能是明智的。在保存操作正在进行时,应用程序本身被中断(即用户点击物理主页按钮,呼叫进入等)的操作。您可以在分派保存操作之前调用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指出的那样,你可能也想要执行原子写法。
答案 1 :(得分:2)
由于您的代码现在正确,是的,会出现问题。这是因为您将其设置为不以原子方式传输:
return [content writeToFile:fullPath atomically:NO];
原子意味着不是删除文件然后开始写入,而是将文件写入单独的临时文件位置。一旦文件完全写入,它就会删除旧版本的文件(如果存在)并将新文件重命名为正确的名称。如果传输未完成,则不会发生任何事情,只应删除临时文件。
因此,如果您在该行中原子地更改为YES
,则调用该数据将返回旧数据,直到保存完成,之后随时将获取新数据。
为此,您需要:
return [content writeToFile:fullPath atomically:YES];