我正在C#中创建一个日志记录类,我需要它是线程安全的。我已经实现了TextWriter.Synchronized和锁,但我在锁似乎不起作用的地方遇到了一个非常奇怪的问题。
我不想使用singleton或静态类,因为我希望能够在任何给定时间拥有该日志记录类的多个实例,并且我希望基于日志的文件名同步线程。因此,如果我有30个线程,其中Log类的3个不同实例都使用同一个日志文件,那么它将正确同步,不会出现任何问题。以下是我到目前为止的想法。我省略了一些无关的代码,比如构造函数和关闭/处置。
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_,并将AutoFlush属性添加到StreamWriter;设置为true。。。仍然不起作用。
我解决了这个问题!
线程同步工作正常,TextWriter.Synchronized()也是如此,所以问题根本不在于线程。考虑到这一点:
我创建了Log类的3个实例,并将它们全部指向"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个唯一且独立的文件句柄。每个文件句柄或Stream都有自己的文件指针,当我读或写时,它会沿着流进行查找。
因此,如果我们有以下内容:
log1.WriteLine("this is some text"); //handled on thread 1
log2.WriteLine("testing"); //handled on thread 2
如果线程1开始写入文件并完成,文件内容将是
这是一些文本
当线程2开始写入时,由于文件句柄和流是唯一的,因此log1的文件指针的当前位置为16,而log2的文件指针仍为0,因此在log2完成写入后,生成的日志文件将读取:
测试一些文本
所以,我所需要做的就是确保每个日志文件只打开1个唯一的FileStream,并像以前一样进行同步。现在效果很好!
我认为你的锁工作得很好,但根据文档,TextWriter.Flush实际上并没有做任何事情,所以在你释放锁之前,它实际上并没有刷新缓冲区。这是[链接]。1
看起来您可以通过在流写入程序上使用Open方法中的AutoFlush来解决问题。
this.m_Writer = TextWriter.Synchronized(new StreamWriter(this.m_File){AutoFlush=true})