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 に設定しても機能しません。