I have a LogManager that uses some async methods to add to and read from an (async)observablecollection (both encounter the same error). However when running I sometimes have a null value added to this collection and I can't find out where it is coming from.
I tried add breakpoints on the method Log and the async method with a simple if, but there I don't get any null values. The only place I encounter them is when I'm trying to save the logs to a file, in which they are already added to the collection..
At first I thought it was because I was calling the function too fast (it is in a while true loop, that can be canceled though) because of items being added to the collection while I was retrieving values from them.
Right now I got it partially 'fixed' by removing the null object from the collection when I encounter one, though I find it disturbing as it might be a Log I shouldn't be missing.
I'll continue searching, if anyone notices something or want me to add other methods I used to understand better please let me know, any help is appreciated!
Here is my asynchronous SaveLogs function and SaveLoop:
private static Task SaveLoop(CancellationTokenSource cancel)
{
return Task.Run(async () =>
{
while (true)
{
int count = MessageList.Count;
if (count > SavedLogCount)
{
await SaveLogs();
}
if (cancel.IsCancellationRequested)
break;
}
}, cancel.Token);
}
private static Task SaveLogs(bool startLogging = false)
{
return Task.Run(() =>
{
int count = MessageList.Count;
int savedLogCount = SavedLogCount;
int logsSaved = 0;
Console.WriteLine("{0} logs to save", (count - savedLogCount));
SavedLogCount = count;
if (count > savedLogCount)
{
string logPath = "logs\\";
string logFile = StartTime.ToString("MM-dd-yy_HH-mm-ss") + ".log";
string fullPath = Path.Combine(logPath, logFile);
if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath);
}
using (StreamWriter writer = new StreamWriter(fullPath, !startLogging))
{
int item = -1;
try
{
for (int i = savedLogCount; i < count; i++)
{
item = i;
LogMessage currentMessage = MessageList[i];
writer.Write("({0})", i);
if (currentMessage == null)
{
Console.WriteLine("Current message is null, setting count back to {0} and removing", i);
writer.WriteLine("Null message");
count = i;
// This is something I don't really want to use
MessageList.Remove(null);
break;
}
else
{
writer.Write("[{0}][{1}] ", currentMessage.Time.ToString("HH:mm:ss:fff"), currentMessage.Level.ToString());
writer.Write(currentMessage.Message);
writer.WriteLine(" ({0} - {1})", currentMessage.Method, currentMessage.Location);
}
logsSaved++;
}
}
catch (Exception e)
{
Console.WriteLine("Error on item {0}", (savedLogCount + item));
Console.WriteLine("Message: {0}", e.Message);
Console.WriteLine(e.StackTrace);
}
}
SavedLogCount = count;
}
});
}
Optimized code (I hope?)
If not, please tell me what I'm doing wrong or where I could improve, thanks in advance!
Improved Log method:
public static void Log(LogMessage logMessage, [CallerMemberName]string method = "", [CallerFilePath]string path = "")
{
logMessage.SetCaller(method, path);
if (!logQueue.Post(logMessage))
Console.WriteLine("Could not add message to queue");
}
New AddLogsToCollection method:
private static async Task AddLogsToCollection(BufferBlock<LogMessage> queue)
{
// Repeat while logQueue is not empty
while (await queue.OutputAvailableAsync())
{
LogMessage logMessage = await queue.ReceiveAsync();
// Add to MessageList
MessageList.Add(logMessage);
// Add to CurrentMessageList
CurrentMessageList.Add(logMessage);
// Add to FilteredMessageList
if (logMessage.Level >= FilterLevel)
{
FilteredMessageList.Add(logMessage);
}
}
}
Improved SaveLoop task:
private static Task SaveLoop(CancellationTokenSource cancel)
{
return Task.Run(async () =>
{
while (true)
{
int logsInQueue = logQueue.Count;
// Check if there are any new logs to be processed
if (logsInQueue != 0)
{
// Copy queue to temporary currentQueue, so new logs can be added at the mean time
BufferBlock<LogMessage> currentQueue = logQueue;
logQueue = new BufferBlock<LogMessage>();
// Mark the currentQueue as complete
currentQueue.Complete();
// Start adding the logs to the collections
Task addLogsToCollection = AddLogsToCollection(currentQueue);
// Wait for the queue to be empty
await Task.WhenAll(addLogsToCollection, currentQueue.Completion);
// Save current logs
SaveLogs();
}
if (cancel.IsCancellationRequested)
break;
}
}, cancel.Token);
}
Changed SaveLogs method:
private static void SaveLogs()
{
string logPath = "logs\\";
string logFile = StartTime.ToString("yyyy-MM-dd_HH-mm-ss") + ".log";
string fullPath = Path.Combine(logPath, logFile);
bool append = File.Exists(fullPath);
if (!Directory.Exists(logPath))
Directory.CreateDirectory(logPath);
int messageCount = MessageList.Count;
using (StreamWriter writer = new StreamWriter(fullPath, append))
{
for (int i = SavedLogCount; i < messageCount; i++)
{
LogMessage currentMessage = MessageList[i];
writer.Write("[{0}][{1}] ", currentMessage.Time.ToString("HH:mm:ss:fff"), currentMessage.Level.ToString());
writer.Write(currentMessage.Message);
writer.WriteLine(" ({0} - {1})", currentMessage.Method, currentMessage.Location);
}
}
SavedLogCount = messageCount;
}
Optimized code 2
AddLogToCollection:
private static Task AddLogToCollection(LogMessage logMessage)
{
return Task.Run(() =>
{
// Add to MessageList
MessageList.Add(logMessage);
// Add to CurrentMessageList
CurrentMessageList.Add(logMessage);
// Add to FilteredMessageList
if (logMessage.Level >= FilterLevel)
{
FilteredMessageList.Add(logMessage);
}
});
}
SaveLoop method:
private static async Task SaveLoop(CancellationTokenSource cancel)
{
string logPath = "logs\\";
string logFile = StartTime.ToString("yyyy-MM-dd_HH-mm-ss") + ".log";
string fullPath = Path.Combine(logPath, logFile);
if (!Directory.Exists(logPath))
Directory.CreateDirectory(logPath);
using (FileStream fileStream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.Read, 8192, useAsync: true))
{
using (StreamWriter writer = new StreamWriter(fileStream))
{
while (true)
{
Console.WriteLine("writting logs");
LogMessage logMessage = await LogQueue.ReceiveAsync(cancel.Token);
await writer.WriteAsync(String.Format("({0}) ", logMessage.LogID));
await writer.WriteAsync(String.Format("[{0}][{1}] ", logMessage.Time.ToString("HH:mm:ss:fff"), logMessage.Level.ToString()));
await writer.WriteAsync(logMessage.Message);
await writer.WriteLineAsync(String.Format(" ({0} - {1})", logMessage.Method, logMessage.Location));
await AddLogToCollection(logMessage);
}
}
}
}
Start and Stop Loop methods:
private static void StartSaveLoop()
{
SaveLoopToken = new CancellationTokenSource();
SaveLoopTask = SaveLoop(SaveLoopToken);
Console.WriteLine("Loop started!");
}
private static void StopSaveLoop()
{
Console.WriteLine("Stop requested");
SaveLoopToken.Cancel();
while (!SaveLoopTask.IsCompleted)
{
Console.WriteLine(SaveLoopTask.Status.ToString());
Thread.Sleep(100);
}
Console.WriteLine("Loop stopped!");
}