同じデータやファイルに複数のスレッドからアクセスされるものを安全に、順番に処理するための排他制御を行えるクラスConcurrentQueueについて簡単に記載していきます。
Queueとは
最初に追加された要素が最初に取り出される先入れ先出し(First In First Out)のデータ構造です。
C#にはQueueクラスもあり、ConcurrentQueueはスレッドセーフなQueueになります。
ConcurrentQueueの使い方
new ConcurrentQueue<型>()で作成。
Enqueueで追加、TryDequeueやTryPeekで要素の取得。
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
{
var queue = new ConcurrentQueue<int>();
// キューに追加
queue.Enqueue(1);
queue.Enqueue(2);
// キューから要素を取得(キューからも削除)
if (queue.TryDequeue(out int result1))
{
Console.WriteLine($"value: {result1}");
}
else
{
Console.WriteLine("Queue is empty.");
}
// キューから要素を取得(キューからは削除されない)
if (queue.TryPeek(out int result2))
{
Console.WriteLine($"value: {result2}");
}
else
{
Console.WriteLine("Queue is empty.");
}
}
実装例
ConcurrentQueueを使用したログ出力を行うクラス
using System.Collections;
using System.Collections.Concurrent;
public class Logger
{
private readonly string LogFilePath = "ファイルのパス";
private readonly ConcurrentQueue<string> _logQueue = new();
private readonly Thread _logThread;
private readonly AutoResetEvent _logEvent = new(false);
private bool _isRunning = true;
public Logger()
{
_logThread = new Thread(ProcessLogQueue)
{
IsBackground = true
};
_logThread.Start();
}
public void Log(string message)
{
string timestampedMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}";
_logQueue.Enqueue(timestampedMessage);
_logEvent.Set(); // 通知してログスレッドを起こす
}
private void ProcessLogQueue()
{
while (_isRunning)
{
_logEvent.WaitOne(); // 通知があるまで待機
while (_logQueue.TryDequeue(out string logMessage))
{
try
{
// ログファイルに書き込み
File.AppendAllText(LogFilePath, logMessage + Environment.NewLine);
}
catch (Exception ex)
{
Console.Error.WriteLine($"ログ書き込みエラー: {ex.Message}");
}
}
}
}
}