2012-08-27 56 views
2

我在C#中創建一個日誌記錄類,我需要它是線程安全的。我實現了TextWriter.Synchronized和鎖,但我遇到了一個非常奇怪的問題,他們似乎無法正常工作。c#線程安全日誌記錄問題不使用singleton

我不想使用單例或靜態類,因爲我希望在任何給定的時間能夠擁有這個日誌記錄類的多個實例,並且我想根據日誌的文件名來同步這些線程。所以如果我有30個線程和3個不同的Log類的實例,它們都使用相同的日誌文件,它將正確同步並且沒有任何問題。以下是我到目前爲止所做的。我遺漏了一些不相關的代碼,比如構造函數和close/dispose。

public class Log : IDisposable 
{ 
    public enum LogType 
    { 
     Information, 
     Warning, 
     Error 
    } 

    private FileStream m_File; 
    private TextWriter m_Writer; 
    private string m_Filename; 

    //this is used to hold sync objects per registered log file 
    private static SortedList<string, object> s_SyncObjects = new SortedList<string, object>(); 
    //this is used to lock and modify the above variable 
    private static readonly object s_SyncRoot = new object(); 

    public void WriteLine(Log.LogType MsgType, string Text) 
    { 
     //this is the problem i think, the lock isn't functioning correctly 
     //see below this code for an example log file with issues 
     lock (Log.s_SyncObjects[this.m_Filename]) 
     { 
      this.m_Writer.WriteLine(DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss:fffffff") + " " + MsgType.ToString() + ": " + Text); 
     } 

     return; 
    } 

    public void Open(string Filename) 
    { 
     //make filename lowercase to ensure it's always the same 
     this.m_Filename = Filename.ToLower(); 
     this.m_File = new FileStream(Filename, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); 
     this.m_Writer = TextWriter.Synchronized(new StreamWriter(this.m_File) { AutoFlush = true }); 

     //lock the syncroot and modify the collection of sync objects 
     //this should make it so that every instance of this class no matter 
     //what thread it's running in will have a unique sync object per log file 
     lock (Log.s_SyncRoot) 
     { 
      if (!Log.s_SyncObjects.ContainsKey(this.m_Filename)) 
       Log.s_SyncObjects.Add(this.m_Filename, new object()); 
     } 
    } 
} 

爲了測試這個我創建指向相同的日誌文件記錄器的3個實例,創建30個線程和分配每個線程記錄器(一個以1,2,3,1,2, 3),然後我運行所有30個線程,直到我按q。

這對於在日誌文件中逐行寫入以及按照正確的順序保持寫入時間的作用很大,但這裏是我在日誌文件中獲得的內容。看起來線程覆蓋了日誌文件的一部分,它似乎在不同線程上記錄器的不同實例發生,而不是不同線程上記錄器的同一實例。下面的日誌文件包含創建條目的時間,記錄器ID(基於1),線程ID(基於0)和消息「test」。

08/27/2012 11:47:34:3469116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3469116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3469116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3469116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3469116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3469116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3479116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3479116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3479116 Information: LOGID: 1, THREADID: 9, MSG: test 
08/27/2012 11:47:34:3479116 Information: LOGID08/27/2012 11:47:34:3479116 Information: LOGID: 3, THREADID: 23, MSG: test 
08/27/2012 11:47:34:3479116 08/27/2012 11:47:34:3509118 Information: LOGID: 1, THREADID: 0, MSG: test 
08/27/2012 11:47:34:3509118 Information: LOGID: 1, THREADID: 0, MSG: test 
08/27/2012 11:47:34:3509118 Information: LOGID: 1, THREADID: 0, MSG: test 
08/27/2012 11:47:34:3509118 Information: LOGID: 1, THREADID: 0, MSG: test 
08/27/2012 11:47:34:3509118 Information: LOGID: 1, THREADID: 0, MSG: test 

請注意,其中2行已被破壞。我猜這是由於鎖不能正常工作,或者我錯誤地使用鎖。我也不想使用排隊或任何類型的單身人士。如果我將WriteLine中的鎖更改爲m_SyncRoot變量並將其設置爲非靜態,此行爲似乎不會發生。我不知道爲什麼這樣做,但對我來說,這似乎不是我想要做的。我也不想單獨鎖定靜態m_SyncRoot,因爲如果我有3個記錄器實例指向3個不同的日誌文件,那麼每個文件都會無緣無故地阻塞另一個。

我對此很失落,我完全搞砸了嗎?

如果有人需要它,這裏是測試類用於生成的線程

public class LogTest 
{ 
    private Log m_Log1; 
    private Log m_Log2; 
    private Log m_Log3; 
    private Thread[] m_Threads; 

    private const int THREAD_COUNT = 30; 
    private bool m_Done; 

    public LogTest() 
    { 
     this.m_Log1 = new Log(); 
     this.m_Log2 = new Log(); 
     this.m_Log3 = new Log(); 

     this.m_Log1.Open("test.txt"); 
     this.m_Log2.Open("test.txt"); 
     this.m_Log3.Open("test.txt"); 

     this.m_Threads = new Thread[THREAD_COUNT]; 
     this.m_Done = false; 
    } 

    public void run() 
    { 
     for (int i = 0; i < THREAD_COUNT; i++) 
     { 
      Thread th = new Thread(new ParameterizedThreadStart(this.LogThread)); 
      this.m_Threads[i] = th; 
     } 

     for (int i = 0; i < THREAD_COUNT; i++) 
     { 
      int logId = 1; 
      Log temp = this.m_Log1; 
      if ((i % 3) == 1) 
      { 
       temp = this.m_Log2; 
       logId = 2; 
      } 
      else if ((i % 3) == 2) 
      { 
       temp = this.m_Log3; 
       logId = 3; 
      } 

      this.m_Threads[i].Start(new object[] { logId, i, temp }); 
     } 

     ConsoleKeyInfo key = new ConsoleKeyInfo(); 
     while ((key = Console.ReadKey()).KeyChar != 'q') 
      ; 

     this.m_Done = true; 
    } 

    private void LogThread(object state) 
    { 
     int loggerId = (int)((object[])state)[0]; 
     int threadId = (int)((object[])state)[1]; 
     Log l = (Log)((object[])state)[2]; 

     while (!this.m_Done) 
     { 
      l.WriteLine(Log.LogType.Information, String.Format("LOGID: {0}, THREADID: {1}, MSG: {2}", loggerId, threadId, "test")); 
     } 
    } 
} 

編輯:編輯以改變靜態M_到S_所建議,並添加了自動沖洗屬性到的StreamWriter;將其設置爲true ...仍然無效。

+0

代碼複審:'m_'前綴不用於靜態變量。 's_'是可以接受的。 – Ankush

+0

看看[VS2010中的concurrent_queue容器](http://blogs.msdn.com/b/nativeconcurrency/archive/2009/11/23/the-concurrent-queue-container-in-vs2010.aspx) – lsalamon

+1

什麼是log4net或NLog? – GSerjo

回答

2

我想出了問題!

線程同步的工作原理與TextWriter.Synchronized()一樣,所以問題根本不在線程中。考慮到這一點:

我創建了3個Log類的實例,並將它們全部指向「test」。TXT」

Log log1 = new Log(); 
Log log2 = new Log(); 
Log log3 = new Log(); 

log1.Open("test.txt"); //new file handle as instance member 
log2.Open("test.txt"); //new file handle as instance member 
log3.Open("test.txt"); //new file handle as instance member 

在每次調用open()的,我打開一個新的文件句柄到同一個文件,所以我有3個獨特的和獨立的文件句柄。每個文件句柄流有它它尋求自己的文件指針沿

所以當我讀到流或寫,如果我們有以下幾點:

log1.WriteLine("this is some text"); //handled on thread 1 
log2.WriteLine("testing"); //handled on thread 2 

如果線程1點開始寫文件並完成文件的內容將是

這是一些文本

當線程2點開始寫,因爲文件句柄和流是唯一LOG1的文件指針的當前位置是在16和LOG2完成寫了這麼後的log 2的仍然是0時,生成的日誌文件將會顯示:

測試一些文字

所以,我需要做的就是確保我打開每個日誌文件只有1獨特的FileStream做同步就像我一直。現在很好用!

1

我認爲你的鎖工作正常,但根據文檔,TextWriter.Flush實際上並沒有做任何事情,因此在釋放鎖之前它實際上並沒有刷新緩衝區。這是[鏈接]。 1

看起來您可以通過在打開方法的Streamwriter上使用AutoFlush來解決問題。

this.m_Writer = TextWriter.Synchronized(new StreamWriter(this.m_File){AutoFlush=true}) 
+0

我非常希望這個工作!不幸的是,我添加了你的建議代碼並刪除了flush ...問題仍然存在。 – vane

+0

這可能是我的想象,但是你的建議修復似乎減少了問題發生的頻率,但問題仍然存在。 – vane

+0

+1這爲我工作。很好的捕獲。 – Ankush