Is BlockingCollection thread safe in every situation ?
I am trying to use a BlockingCollection to implement a log system without taking too much of the Main Thread resources. The idea is that it costs only a BlockingCollection.Add() call for the Main Thread, and one waiting task do the writing job.
The BlockingCollection Class is supposed to take care of the tasks competition with its queue, so that every Add() will bring about a job successively.
Declaring the BlockingCollection object:
private static BlockingCollection<string> queueLogMinimal = new BlockingCollection<string>();
And the other vars:
internal static string AllLogFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Log.log");
private static Object logLocker = new Object();
Initiating the Task which will be always listening, in the constructor :
public LogManager()
{
Task.Factory.StartNew(() =>
{
Thread.CurrentThread.Name = "Logger";
foreach (string message in queueLogMinimal.GetConsumingEnumerable())
{
WriteLogMinimal(message);
}
});
}
A minimal function which is called to manage the logging :
public static void LogMinimal(string message)
{
queueLogMinimal.Add(message);
}
A lock has been necessary, to ensure the Main Thread is not changing the file configuration while the Logger Thread is writing.
The function called by the Task at each BlockingCollection dequeue :
public static void WriteLogMinimal(string message)
{
lock (logLocker)
{
try
{
File.AppendAllText(AllLogFileName, DateTime.Now.ToString() + " - " +message +Environment.NewLine);
}
catch (Exception e)
{
Debug.WriteLine("EXCEPTION while loging the message : ");
}
}
}
The thing is that when testing the functioning of that system by doing logs in a row. If I test, let's say 40 logs in a row:
[TestClass]
public class UnitTest
{
[TestMethod]
public void TestLogs()
{
LogManager lm = new LogManager();
int a = 0;
LogManager.LogMinimal(a++.ToString());
LogManager.LogMinimal(a++.ToString());
LogManager.LogMinimal(a++.ToString());
LogManager.LogMinimal(a++.ToString());
}
}
After 10 to 30 logs, it stops logging because that happens in LaunchLogTask(), in the foreach browsing the BlockingCollection elements with GetConsumingEnumerable() :
Thread was being aborted.
StackTrace :
at System.Threading.Monitor.ObjWait(Boolean exitContext, Int32 millisecondsTimeout, Object obj)\r\n
at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout, Boolean exitContext)\r\n
at System.Threading.SemaphoreSlim.WaitUntilCountOrTimeout(Int32 millisecondsTimeout, UInt32 startTime, CancellationToken cancellationToken)\r\n at System.Threading.SemaphoreSlim.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)\r\n
at System.Collections.Concurrent.BlockingCollection`1.TryTakeWithNoTimeValidation(T& item, Int32 millisecondsTimeout, CancellationToken cancellationToken, CancellationTokenSource combinedTokenSource)\r\n
at System.Collections.Concurrent.BlockingCollection`1.d__68.MoveNext()\r\n
at RenaultTrucks.Common.Managers.LogManager.<>c.<.ctor>b__14_1() in C:\Source\Diagnostica\GLB-DIAGNOSTICA\NewDiagnostica\Source\Common\Managers\LogManager.cs:line 61" string
And if I test it less intensively while addingsleep time :
[TestClass]
public class UnitTest
{
[TestMethod]
public void TestLogs()
{
LogManager lm = new LogManager();
int a = 0;
LogManager.LogMinimal(a++.ToString());
System.Threading.Thread.Sleep(100);
LogManager.LogMinimal(a++.ToString());
System.Threading.Thread.Sleep(100);
LogManager.LogMinimal(a++.ToString());
System.Threading.Thread.Sleep(100);
LogManager.LogMinimal(a++.ToString());
}
}
It logs everything! (without thread abortion).
Is it possible that the queue is managed differently according to the queuing pace, or that that pace can be "too quick" ?