2012-08-27 58 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