C#多執行緒讀寫同一檔案處理

2018-01-09 17:22:00

在多執行緒存取讀寫同一個檔案時,經常遇到異常:「檔案正在由另一進程使用,因此該進程無法存取此檔案」。

多執行緒存取統一資源的異常,

解決方案1,保證讀寫操作單執行緒執行,可以使用lock

解決方案2,使用System.Threading.ReaderWriterLockSlim ,對讀寫操作鎖定處理

讀寫鎖是以 ReaderWriterLockSlim 物件作爲鎖管理資源的,不同的 ReaderWriterLockSlim 物件中鎖定同一個檔案也會被視爲不同的鎖進行管理,這種差異可能會再次導致檔案的併發寫入問題,所以 ReaderWriterLockSlim 應儘量定義爲只讀的靜態物件。

 

ReaderWriterLockSlim 有幾個關鍵的方法,本文僅討論寫入鎖:

呼叫 EnterWriteLock 方法 進入寫入狀態,在呼叫執行緒進入鎖定狀態之前一直處於阻塞狀態,因此可能永遠都不返回
呼叫 TryEnterWriteLock 方法 進入寫入狀態,可指定阻塞的間隔時間,如果呼叫執行緒在此間隔期間並未進入寫入模式,將返回false
呼叫 ExitWriteLock 方法 退出寫入狀態,應使用 finally 塊執行 ExitWriteLock 方法,從而確保呼叫方退出寫入模式。

一、不是用鎖處理,多執行緒存取檔案不定時拋出異常

static void Main(string[] args)
{
    //迭代執行寫入日誌記錄,由於多個執行緒同時寫入同一個檔案將會導致錯誤
    Parallel.For(0, LogCount, e =>
    {
        WriteLog();
    });
    Console.Read();
}
static int LogCount = 100;
static int FailedCount = 0;
static int WriteCount = 0;
static void WriteLog()
{
    try
    {
        WriteCount++;

        LogHelper.LogHelper _log = new LogHelper.LogHelper("g:\\temp2\\one.txt", true);
        DateTime now = DateTime.Now;
        var logContent = string.Format("Tid: {0}{1} {2}=>{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), WriteCount);
        _log.WriteLine(logContent);
    }
    catch (Exception ex)
    {
        FailedCount++;
        Console.WriteLine("累計出錯數:" + FailedCount);
        Console.WriteLine(ex.Message);
    }
}

二、使用讀寫鎖 同步寫入檔案處理

//讀寫鎖,當資源處於寫入模式時,其他執行緒寫入需要等待本次寫入結束之後才能 纔能繼續寫入
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
static void WriteLog()
{
    try
    {
        //設定讀寫鎖爲寫入模式獨佔資源,其他寫入請求需要等待本次寫入結束之後才能 纔能繼續寫入
        //注意:長時間持有讀執行緒鎖或寫執行緒鎖會使其他執行緒發生飢餓 (starve)。 爲了得到最好的效能,需要考慮重新構造應用程式以將寫存取的持續時間減少到最小。
        //從效能方面考慮,請求進入寫入模式應該緊跟檔案操作之前,在此處進入寫入模式僅是爲了降低程式碼複雜度
        //因進入與退出寫入模式應在同一個try finally語句塊內,所以在請求進入寫入模式之前不能觸發異常,否則釋放次數大於請求次數將會觸發異常
        LogWriteLock.EnterWriteLock();


        WriteCount++;
        LogHelper.LogHelper _log = new LogHelper.LogHelper("g:\\temp2\\one.txt", true);
        DateTime now = DateTime.Now;
        var logContent = string.Format("Tid: {0}{1} {2}=>{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), WriteCount);
        _log.WriteLine(logContent);
    }
    catch (Exception ex)
    {
        FailedCount++;
        Console.WriteLine("累計出錯數:" + FailedCount);
        Console.WriteLine(ex.Message);
    }
    finally
    {
        //退出寫入模式,釋放資源佔用
        //注意:一次請求對應一次釋放
        //若釋放次數大於請求次數將會觸發異常[寫入鎖定未經保持即被釋放]
        //若請求處理完成後未釋放將會觸發異常[此模式不下允許以遞回方式獲取寫入鎖定]
        LogWriteLock.ExitWriteLock();
    }
}

三、補充:初始化FileStream時使用包含檔案共用屬性(System.IO.FileShare)的建構函式比使用自定義執行緒鎖更爲安全高效

 1     class Program
 2     {
 3         static int LogCount = 100;
 4         static int WritedCount = 0;
 5         static int FailedCount = 0;
 6 
 7         static void Main(string[] args)
 8         {
 9             //迭代執行寫入日誌記錄
10             Parallel.For(0, LogCount, e =>
11             {
12                 WriteLog();
13             });
14 
15             Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
16             Console.Read();
17         }
18 
19         static void WriteLog()
20         {
21             try
22             {
23                 var logFilePath = "log.txt";
24                 var now = DateTime.Now;
25                 var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());
26 
27                 var logContentBytes = Encoding.Default.GetBytes(logContent);
28                 //由於設定了檔案共用模式爲允許隨後寫入,所以即使多個執行緒同時寫入檔案,也會等待之前的執行緒寫入結束之後再執行,而不會出現錯誤
29                 using (FileStream logFile = new FileStream(logFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
30                 {
31                     logFile.Seek(0, SeekOrigin.End);
32                     logFile.Write(logContentBytes, 0, logContentBytes.Length);
33                 }
34 
35                 WritedCount++;
36             }
37             catch (Exception ex)
38             {
39                 FailedCount++;
40                 Console.WriteLine(ex.Message);
41             }
42         }
43     }

 

更多:

C# 獲取當前路徑方法整理

C#獲取當前系統磁碟符、系統目錄、桌面等

C#獲取磁碟列表與資訊

 

轉載於:https://www.cnblogs.com/tianma3798/p/8252553.html