2013-08-19 22 views
11

我目前正在寫一些文件在後臺線程盤只是通過調用IOS寫入到磁盤上的後臺線程

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

公司將內容被破壞? 或者寫入操作會鎖定文件,讀取操作會一直等到寫入完成?

回答

16

我會傾向於dispatch_sync你的讀操作的my_queue保證線程安全(假設它是一個串行隊列)。您也可以使用各種synchronization tools(如鎖或@synchronized指令)中的任何一種,但考慮到您已將您的隊列設置爲文件交互,使用該串行隊列可能是最簡單的。

這種使用隊列來協調與共享資源交互的技術在併發編程指南的Eliminating Lock-Based Code部分討論。


順便說一句,如果你在後臺隊列保存(這意味着保存操作是足夠的理由在後臺做可能慢),這可能是謹慎,以確保您的要求在保存操作正在進行的過程中,如果應用本身中斷(即,用戶點擊物理主頁按鈕,進入呼叫等),則需要一點時間來完成操作。

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; 
    } 
}); 

這將確保您的保存操作有一個戰鬥的機會成功完成,即使該應用程序被中斷:您可以通過調用beginBackgroundTaskWithExpirationHandler你發送之前保存操作,並調用endBackgroundTask當它這樣做做到這一點。

而且,正如Jsdodgers指出的那樣,您可能也想要執行原子寫入。

+0

這是我最終使用的解決方案。從同一隊列的磁盤寫入和讀取。非常感謝。 – wjheng

2

由於您的代碼是現在,是的,會有一個問題。這是因爲你將它設置爲不原子轉移:

return [content writeToFile:fullPath atomically:NO]; 

什麼原子的意思是說,而不是刪除文件,然後開始寫,它的文件寫入到一個單獨的,臨時的文件位置。一旦文件被完全寫入,它會刪除舊版本的文件(如果存在的話)並將新文件重命名爲正確的名稱。如果傳輸未完成,則不會發生任何事情,臨時文件應該被刪除。

因此,如果您在該行中原子級地更改爲YES,那麼調用該數據將返回舊數據,直到保存完成,並且隨後會隨時爲您提供新數據。

因此,要做到這一點,你會想:

return [content writeToFile:fullPath atomically:YES]; 
+0

我從來沒有說過它可以保證線程安全。只有這樣,它才能防止文件被破壞,並且使得OP很可能會得到他所詢問的錯誤(儘管未知數不太可能)。 (這是數據對象被損壞)。我不知道如果他在名稱被更改的時刻嘗試訪問數據會發生什麼(但是該方法是這樣做的)。因此,正如你和蘋果指出的那樣,它不是線程安全的。 – Jsdodgers

+0

另外,它們所表示的不是線程安全的是,當在多線程中多線程處理和設置/獲取相同的對象時,不能保證您獲得的值是否是其設置之前或之後的值。然而,原子性保證無論你得到了什麼,它都將是完整的,未被破壞的對象(這就是問題所要求的)。 – Jsdodgers

+1

我相信這就是'initWithFile'等對象的方法有一個版本以'error'作爲參數的原因。如果在這個過程中數據發生了變化,那麼很可能返回nil作爲對象(這是文檔在發生錯誤時會說的事情)並解釋你傳入的'error'對象發生了什麼,而是比給你腐敗的數據。 – Jsdodgers