使用异步方法在ObservableCollection中出现空对象

时间:2013-06-11 13:36:47

标签: c#

我有一个LogManager,它使用一些异步方法来添加和读取(异步)observablecollection(两者都遇到相同的错误)。但是在运行时,我有时会在此集合中添加一个空值,但我无法找到它的来源。

我尝试使用简单的if在方法Log和async方法上添加断点,但是我没有得到任何空值。我遇到它们的唯一地方是当我试图将日志保存到文件中时,它们已经添加到集合中了。

起初我以为是因为我调用函数的速度太快(虽然可以取消,但是可以取消),因为当我从它们中检索值时,项目被添加到集合中。

现在,当我遇到一个空对象时,我通过从集合中删除空对象得到了部分'固定',但我发现它令人不安,因为它可能是一个我不应该丢失的日志。

我会继续搜索,如果有人注意到某些内容或希望我添加其他方法,我曾经更好地了解请告诉我,任何帮助表示赞赏!

这是我的异步SaveLogs函数和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;
                }
            });
    }

优化代码(我希望?)

如果没有,请提前告诉我,我做错了什么或者我可以改进的地方!

改进的日志方法:

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

新的AddLogsToCollection方法:

    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);
            }
        }
    }

改进了SaveLoop任务:

    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);
    }

更改了SaveLogs方法:

    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;
    }

优化代码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方法:

    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);
                }
            }
        }
    }

启动和停止循环方法:

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

1 个答案:

答案 0 :(得分:3)

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

如果您的平台上没有BufferBlock,则可以使用an async-ready producer/consumer queue I built around a Queue。它使用async lockscondition variables)中的AsyncEx librarycontains the async-ready producer/consumer queue

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

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));
    }
  }
}