0

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!");
    }
4

1 回答 1

3

我建议您使用BufferBlock<LogMessage>; 这是一个async准备好的生产者/消费者队列。

如果BufferBlock在您的平台上不可用,您可以使用async围绕Queue. 它使用我的AsyncEx 库中的async条件变量(其中还包含-ready producer/consumer queue)。async

更新:您的帖子看起来不错,但您的接收不必要地复杂。我会做这样的事情:

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)
    {
      LogMessage logMessage = await queue.ReceiveAsync(cancel);
      await writer.WriteAsync(string.Format("[{0}][{1}] ", currentMessage.Time.ToString("HH:mm:ss:fff"), currentMessage.Level.ToString()));
      await writer.WriteAsync(currentMessage.Message);
      await writer.WriteLineAsync(string.Format(" ({0} - {1})", currentMessage.Method, currentMessage.Location));
    }
  }
}
于 2013-06-11T14:12:02.173 回答