2

C# でロギング クラスを作成していますが、スレッド セーフにする必要があります。TextWriter.Synchronized とロックを実装しましたが、ロックが機能していないように見える非常に奇妙な問題が発生しています。

シングルトンまたは静的クラスを使用したくありません。これは、このロギング クラスのインスタンスをいつでも複数持つことができるようにしたいためであり、ログのファイル名に基づいてスレッドを同期したいからです。したがって、Log クラスの 3 つの異なるインスタンスを持つ 30 のスレッドがすべて同じログ ファイルを使用している場合、同期は適切に行われ、問題は発生しません。以下は、これまでに思いついたものです。コンストラクターやクローズ/ディスポーズなど、無関係なコードの一部を省略しました。

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 つを (1、2、3、1、2、3 の順序で) 割り当ててから、すべてを実行します。 qを押すまで30スレッド。

これは、ログ ファイルに行ごとに書き込み、書き込みが正しい順序で発生する時間を維持するのに最適ですが、ログ ファイルに記録される内容は次のとおりです。スレッドがログ ファイルの一部を上書きしているようで、異なるスレッドのロガーの異なるインスタンスで発生するようであり、異なるスレッドのロガーの同じインスタンスでは決して発生しないようです。以下のログ ファイルには、エントリが作成された時刻、ロガー 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 つの一意の個別のファイル ハンドルがあります。各ファイル ハンドルまたはストリームには、読み取りまたは書き込み時にストリームに沿ってシークする独自のファイル ポインターがあります。

したがって、次の場合:

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

スレッド 1 がファイルへの書き込みを開始して完了すると、ファイルの内容は次のようになります。

これはテキストです

スレッド 2 が書き込みを開始すると、ファイル ハンドルとストリームが一意であるため、log1 のファイル ポインターの現在の位置は 16 であり、log2 のファイル ポインターはまだ 0 であるため、log2 の書き込みが完了すると、結果のログ ファイルは次のようになります。

テキストのテスト

したがって、ログ ファイルごとに一意の FileStream を 1 つだけ開いて、以前と同じように同期を行うようにするだけです。今すぐ大活躍!

于 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 に答える