2

我正在用 C# 创建一个日志记录类,我需要它是线程安全的。我已经实现了 TextWriter.Synchronized 和锁,但是我遇到了一个非常奇怪的问题,锁似乎不起作用。

我不想使用单例或静态类,因为我希望能够在任何给定时间拥有此日志记录类的多个实例,并且我想根据日志的文件名同步线程。因此,如果我有 30 个线程和 3 个不同的 Log 类实例都使用相同的日志文件,它将正确同步并且没有任何问题。以下是我到目前为止的想法。我省略了一些不相关的代码,例如构造函数和关闭/处置。

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... 仍然无法正常工作。

4

2 回答 2

2

我解决了这个问题!

线程同步正常工作,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 并像以前一样进行同步。现在效果很好!

于 2012-08-28T02:39:06.983 回答
1

我认为您的锁工作正常,但根据文档, TextWriter.Flush 实际上并没有做任何事情,因此在您释放锁之前它实际上并没有刷新缓冲区。这是[链接]。1

看起来您可以通过在 Open 方法中的流写入器上使用 AutoFlush 来解决问题。

this.m_Writer = TextWriter.Synchronized(new StreamWriter(this.m_File){AutoFlush=true})
于 2012-08-27T19:08:08.720 回答