【问题标题】:ios write to disk on background threadios在后台线程上写入磁盘
【发布时间】:2013-08-19 02:21:35
【问题描述】:

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

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];

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

【问题讨论】:

    标签: ios multithreading


    【解决方案1】:

    我倾向于dispatch_sync 您对my_queue 的读取操作以确保线程安全(假设它是一个串行队列)。您也可以使用各种synchronization tools 中的任何一种(例如锁或@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 所指出的,您可能还想执行原子写入。

    【讨论】:

    • 这是我最终使用的解决方案。从同一队列写入和读取磁盘。非常感谢。
    【解决方案2】:

    由于您的代码现在是这样,是的,会有问题。这是因为您将其设置为不自动传输:

    return [content writeToFile:fullPath atomically:NO];
    

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

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

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

    return [content writeToFile:fullPath atomically:YES];
    

    【讨论】:

    • 我从来没有说过它保证线程安全。只是它可以防止文件损坏,并使 OP 不太可能(尽管不太可能)收到他所询问的错误。 (这是被破坏的数据对象)。我不知道如果他在更改名称时尝试访问数据会发生什么(但是方法会这样做)。因此,正如您和 Apple 所指出的,它不是线程安全的。
    • 另外,它们不是线程安全的意思是当多线程和在多个线程中设置/获取相同的对象时,你不能保证你得到的值是它之前的值还是设置好之后。然而,原子性保证无论你得到什么,它都是完整的、未损坏的对象(这就是问题所要求的)。
    • 我相信这就是对象的 initWithFile 等方法具有将 error 作为参数的版本的原因。如果在此过程中更改了数据,它很可能会返回 nil 作为对象(这是文档所说的在发生错误时会发生的情况)并解释您传入的 error 对象中发生的事情,而不是而不是给你损坏的数据。
    猜你喜欢
    • 2023-03-22
    • 2015-11-05
    • 1970-01-01
    • 2014-09-17
    • 1970-01-01
    • 2016-12-12
    • 2014-09-14
    • 2011-11-17
    • 1970-01-01
    相关资源
    最近更新 更多