2010-04-08 125 views
8

我的Web應用程序從文件系統返回文件。這些文件是動態的,所以我無法知道名稱會有多少。當這個文件不存在時,應用程序從數據庫中創建它。我想避免兩個不同的線程同時重新創建同一個文件,或者一個線程嘗試在其他線程創建它時返回該文件。如何鎖定文件並在寫入時避免讀取

此外,我不想鎖定所有文件通用的元素。因此,我應該在創建文件時鎖定文件。

所以我想鎖定一個文件,直到它的重新創建完成,如果其他線程嘗試訪問它......它將不得不等待文件被解鎖。

我一直在閱讀關於FileStream.Lock,但我必須知道文件的長度,它不會阻止其他線程嘗試讀取文件,所以它不適用於我的特殊情況。

我一直在閱讀關於FileShare.None,但它會拋出一個異常(哪種異常類型?)如果其他線程/進程試圖訪問該文件...所以我應該開發一個「錯誤「,因爲我想避免產生異常...我不喜歡這種方法,儘管也許沒有更好的方法。

與FileShare.None的方法應該是這樣或多或少:

static void Main(string[] args) 
    { 
     new Thread(new ThreadStart(WriteFile)).Start(); 
     Thread.Sleep(1000); 
     new Thread(new ThreadStart(ReadFile)).Start(); 

     Console.ReadKey(true); 
    } 

    static void WriteFile() 
    { 
     using (FileStream fs = new FileStream("lala.txt", FileMode.Create, FileAccess.Write, FileShare.None)) 
     using (StreamWriter sw = new StreamWriter(fs)) 
     { 
      Thread.Sleep(3000); 
      sw.WriteLine("trolololoooooooooo lolololo"); 
     } 
    } 

    static void ReadFile() 
    { 
     Boolean readed = false; 
     Int32 maxTries = 5; 

     while (!readed && maxTries > 0) 
     { 
      try 
      { 
       Console.WriteLine("Reading..."); 
       using (FileStream fs = new FileStream("lala.txt", FileMode.Open, FileAccess.Read, FileShare.Read)) 
       using (StreamReader sr = new StreamReader(fs)) 
       { 
        while (!sr.EndOfStream) 
         Console.WriteLine(sr.ReadToEnd()); 
       } 
       readed = true; 
       Console.WriteLine("Readed"); 
      } 
      catch (IOException) 
      { 
       Console.WriteLine("Fail: " + maxTries.ToString()); 
       maxTries--; 
       Thread.Sleep(1000); 
      } 
     } 
    } 

但我不喜歡這樣的事實,我要捕捉異常,多試幾次,等待時間不準確的量: |

+2

它是FileShare.None而不是FileAccess.None(FileAccess定義您的應用程序具有訪問權限,而FileShare用於鎖定文件,如您所願) – jpabluz 2010-04-08 14:04:42

+2

在其他主題上,請記住在您的應用程序被銷燬時解鎖文件,我討厭事後鎖定。 – jpabluz 2010-04-08 14:06:01

+0

我編輯並修復了它,謝謝! – vtortola 2010-04-08 14:15:44

回答

3

您可以通過對流構造函數使用FileMode.CreateNew參數來處理此操作。其中一個線程將會丟失,並發現該文件已由另一個線程提前一微秒創建。並會得到一個IOException。

然後它需要旋轉,等待文件被完全創建。你用FileShare.None強制執行。在這裏捕捉異常並不重要,它仍在旋轉。除非你是P/Invoke,否則沒有其他解決方法。

+0

看起來你是對的,沒有其他解決方法。 謝謝! – vtortola 2010-04-08 15:30:16

1

我認爲,正確的形式給出將是以下幾點: 創建一組字符串被你也會保存當前的文件名 這樣一個線程將處理在時間的文件,像這樣

//somewhere on your code or put on a singleton 
static System.Collections.Generic.HashSet<String> filesAlreadyProcessed= new System.Collections.Generic.HashSet<String>(); 


//thread main method code 
bool filealreadyprocessed = false 
lock(filesAlreadyProcessed){ 
    if(set.Contains(filename)){ 
    filealreadyprocessed= true; 
    } 
    else{ 
    set.Add(filename) 
    } 
} 
if(!filealreadyprocessed){ 
//ProcessFile 
} 
+0

這就是問題所在,我不想鎖定所有文件的通用元素。首先,獲得一個鎖是很昂貴的,而且我不想爲每個要求文件的調用獲取一個鎖,無論該文件是否已經存在。其次,我不想阻止嘗試獲取不同文件的線程,因爲我正在創建其中的一個線程。 由於這些原因,我想鎖定文件本身。 乾杯。 – vtortola 2010-04-08 14:20:10

+0

您是否測量了獲取鎖定和鎖定直到完成的時間與線程喚醒,檢查訪問,獲取異常,休眠和重複幾次的時間?我預計鎖定策略在這裏會更受歡迎。 'Thread.Sleep'不太適合阻塞鎖。如果寫入線程提前結束呢?讀線程不會喚醒。您可能需要考慮一個'ManualResetEvent'來控制兩個線程之間的訪問。 – 2010-05-10 13:58:24

1

您是否有辦法識別正在創建的文件?

說這些文件中的每一個都對應於數據庫中的唯一ID。你創建一個集中的位置(Singleton?),這些ID可以與可鎖定的東西(Dictionary)相關聯。需要讀出的線程/寫入這些文件中的一個執行以下操作:

//Request access 
ReaderWriterLockSlim fileLock = null; 
bool needCreate = false; 
lock(Coordination.Instance) 
{ 
    if(Coordination.Instance.ContainsKey(theId)) 
    { 
     fileLock = Coordination.Instance[theId]; 
    } 
    else if(!fileExists(theId)) //check if the file exists at this moment 
    { 
     Coordination.Instance[theId] = fileLock = new ReaderWriterLockSlim(); 
     fileLock.EnterWriteLock(); //give no other thread the chance to get into write mode 
     needCreate = true; 
    } 
    else 
    { 
     //The file exists, and whoever created it, is done with writing. No need to synchronize in this case. 
    } 
} 

if(needCreate) 
{ 
    createFile(theId); //Writes the file from the database 
    lock(Coordination.Instance) 
     Coordination.Instance.Remove[theId]; 
    fileLock.ExitWriteLock(); 
    fileLock = null; 
} 

if(fileLock != null) 
    fileLock.EnterReadLock(); 

//read your data from the file 

if(fileLock != null) 
    fileLock.ExitReadLock(); 

當然,不遵循這個確切的鎖定協議將有機會獲得該文件的線程。

現在,鎖定Singleton對象肯定不是很理想,但如果您的應用程序需要全局同步,那麼這是一種實現它的方法。

+0

與@hworangdo代碼相同的問題,即使在您不需要它的每個請求中,您也必須獲得一個鎖。 – vtortola 2010-04-08 15:27:39

+1

@vtortola:是的。爲了保護我的答案:獲得鎖並不昂貴(衡量它,它真的沒有什麼,特別是與文件IO相比),但等待另一個線程釋放鎖。您可以嘗試查找無鎖字典實現。在需要創建文件的情況下,您只需要小心,以便只有一個線程被賦予創建它的任務。 – 2010-04-08 16:10:27

+0

你可能是對的,我從來沒有測試過自己是如何獲取鎖的,我知道它是因爲我讀了它。等待另一個線程釋放鎖定肯定會更昂貴,但每個文件只會發生一次。稍後我會測試你的方法,也許你的速度更快。謝謝! – vtortola 2010-04-08 16:52:52

1

你的問題真的讓我思考。

而不是讓每個線程負責文件訪問並讓它們阻塞,如果你使用了一個需要持久保存的文件隊列,並且有一個後臺工作線程出隊並持久化?

當後臺工作人員開始工作時,可以讓Web應用程序線程返回db值,直到文件確實存在。

我已經發布了一個非常簡單的example of this on GitHub

隨意給它一個鏡頭,讓我知道你的想法。

僅供參考,如果你沒有git的,你可以使用svn把它http://svn.github.com/statianzo/MultiThreadFileAccessWebApp

0

你爲什麼不只是使用的數據庫 - 例如如果您有辦法將文件名與其包含的數據庫相關聯,只需向數據庫添加一些信息,以指定當前是否存在該文件以及何時創建該文件,文件中信息的陳舊程度如何等等。當一個線程需要一些信息時,它會檢查數據庫以查看該文件是否存在,如果不存在,則向表中寫出一行表示正在創建文件。完成後,用布爾值更新該行,表示該文件已準備好供其他人使用。

這種方法的好處 - 所有的信息都在1個地方 - 所以你可以做很好的錯誤恢復 - 例如,如果創建文件的線程由於某種原因死亡嚴重,另一個線程可能會出現並決定重寫該文件,因爲創建時間太舊。您還可以創建簡單的批處理清理流程,並獲取關於文件使用某些數據的頻率的準確數據,更新信息的頻率(通過查看創建時間等)。此外,避免在文件系統中執行許多磁盤搜索,因爲不同的線程在整個地方尋找不同的文件 - 特別是如果您決定讓多個前端機器在通用磁盤上尋找時。

棘手的事情 - 您必須確保您的數據庫支持在創建文件時線程寫入的表上的行級鎖定,否則表本身可能會被鎖定,這可能會導致這種情況變得無法接受。

0

這個問題很老,現在已經有了明顯的答案。不過,我想發佈一個更簡單的選擇。

我認爲,我們可以直接使用lock語句上的文件名,如下所示:

lock(string.Intern("FileLock:absoluteFilePath.txt")) 
{ 
    // your code here 
} 

一般來說,鎖定一個字符串是因爲字符串實習的壞主意。但在這種特殊情況下,它應該確保沒有其他人能夠訪問該鎖。在嘗試閱讀之前只需使用相同的鎖定字符串。這裏實習對我們而言並不違反。

PS:文本「的FileLock」只是爲了確保其他字符串文件路徑不會受到影響的任意文本。

相關問題